Parsing 如何使时间解析谓词在两个方向上工作?

Parsing 如何使时间解析谓词在两个方向上工作?,parsing,prolog,dcg,Parsing,Prolog,Dcg,使用SWI-Prolog,我制作了这个简单的谓词,它将hh:mm格式的时间关联为一个时间项 time_string(time(H,M), String) :- number_string(H,Hour), number_string(M,Min), string_concat(Hour,":",S), string_concat(S,Min,String). 但谓词只能在一个方向上工作 time_string(time(10,30),Stri

使用SWI-Prolog,我制作了这个简单的谓词,它将hh:mm格式的时间关联为一个时间项

time_string(time(H,M), String) :-
    number_string(H,Hour),
    number_string(M,Min),
    string_concat(Hour,":",S),
    string_concat(S,Min,String).
但谓词只能在一个方向上工作

time_string(time(10,30),String).
String = "10:30".      % This is perfect.
不幸的是,这个查询失败了

time_string(Time,"10:30").
ERROR: Arguments are not sufficiently instantiated
ERROR: In:
ERROR:   [11] number_string(_8690,_8692)
ERROR:   [10] time_string(time(_8722,_8724),"10:30") at /tmp/prolcompDJBcEE.pl:74
ERROR:    [9] toplevel_call(user:user: ...) at /usr/local/logic/lib/swipl/boot/toplevel.pl:1107

如果我不必编写一个全新的谓词来回答这个查询,那就太好了。有什么方法可以做到这一点吗?

好吧,从结构化术语timeH,M到字符串要比从非结构化字符串timeH,M容易

谓词在生成方向上起作用

time_string(time(10,30),String).
String = "10:30".      % This is perfect.
对于另一个方向,您需要解析字符串。在这种情况下,这在计算上很容易,而且不需要搜索/回溯就可以完成,这很好

使用Prolog的语法,这是编写处理一系列内容的谓词的好方法。在本例中,内容列表是长度为1的字符原子列表。有关SWI Prolog的相关页面,请参见

幸运的是,DCG代码可以向后/向前运行,但通常情况并非如此。满足某些效率或因果关系要求的实际代码可能会迫使它在单个谓词的保护下,首先按处理方向进行分支,然后运行完全不同的代码结构来交付货物

所以在这里。代码立即衰减到解析中并生成分支。Prolog还不能完全基于约束运行。你只需要先做一些事情

无论如何,让我们这样做:

:- use_module(library(dcg/basics)).

% ---
% "Generate" direction; note that String may be bound to something
% in which case this clause also verifies whether generating "HH:MM"
% from time(H,M) indeed yields (whatever is denoted by) String.
% ---

process_time(time(H,M),String) :-
   integer(H),                            % Demand that H,M are valid integers inside limits
   integer(M),
   between(0,23,H),
   between(0,59,M),
   !,                                     % Guard passed, commit to this code branch
   phrase(time_g(H,M),Chars,[]),          % Build Codes from time/2 Term
   string_chars(String,Chars).            % Merge Codes into a string, unify with String

% ---
% "Parse" direction. 
% ---

process_time(time(H,M),String) :-
   string(String),                        % Demand that String be a valid string; no demands on H,M  
   !,                                     % Guard passed, commit to this code branch
   string_chars(String,Chars),            % Explode String into characters
   phrase(time_p(H,M),Chars,[]).          % Parse "Codes" into H and M

% ---
% "Generate" DCG
% ---
   
time_g(H,M) --> hour_g(H), [':'], minute_g(M).
hour_g(H)   --> { divmod(H,10,V1,V2), digit_int(D1,V1), digit_int(D2,V2) }, digit(D1), digit(D2).
minute_g(M) --> { divmod(M,10,V1,V2), digit_int(D1,V1), digit_int(D2,V2) }, digit(D1), digit(D2).

% ---
% "Parse" DCG
% ---
   
time_p(H,M) --> hour_p(H), [':'], minute_p(M).
hour_p(H)   --> digit(D1), digit(D2), { digit_int(D1,V1), digit_int(D2,V2), H is V1*10+V2, between(0,23,H) }.
minute_p(M) --> digit(D1), digit(D2), { digit_int(D1,V1), digit_int(D2,V2), M is V1*10+V2, between(0,59,M) }.   
   
% ---
% Do I really have to code this? Oh well!
% ---

digit_int('0',0).
digit_int('1',1).
digit_int('2',2).
digit_int('3',3).
digit_int('4',4).
digit_int('5',5).
digit_int('6',6).
digit_int('7',7).
digit_int('8',8).
digit_int('9',9).

% ---
% Let's add plunit tests!
% ---

:- begin_tests(hhmm).

test("parse 1",    true(T == time(0,0)))   :- process_time(T,"00:00").
test("parse 2",    true(T == time(12,13))) :- process_time(T,"12:13").
test("parse 1",    true(T == time(23,59))) :- process_time(T,"23:59").
test("generate",   true(S == "12:13"))     :- process_time(time(12,13),S).
test("verify",     true)                   :- process_time(time(12,13),"12:13").
test("complete",   true(H == 12))          :- process_time(time(H,13),"12:13").

test("bad parse",    fail)                 :- process_time(_,"66:66").
test("bad generate", fail)                 :- process_time(time(66,66),_).

:- end_tests(hhmm).
这是很多代码

它有用吗

?- run_tests.
% PL-Unit: hhmm ........ done
% All 8 tests passed
true.

从结构化术语timeH,M到字符串比从非结构化字符串timeH,M容易

谓词在生成方向上起作用

time_string(time(10,30),String).
String = "10:30".      % This is perfect.
对于另一个方向,您需要解析字符串。在这种情况下,这在计算上很容易,而且不需要搜索/回溯就可以完成,这很好

使用Prolog的语法,这是编写处理一系列内容的谓词的好方法。在本例中,内容列表是长度为1的字符原子列表。有关SWI Prolog的相关页面,请参见

幸运的是,DCG代码可以向后/向前运行,但通常情况并非如此。满足某些效率或因果关系要求的实际代码可能会迫使它在单个谓词的保护下,首先按处理方向进行分支,然后运行完全不同的代码结构来交付货物

所以在这里。代码立即衰减到解析中并生成分支。Prolog还不能完全基于约束运行。你只需要先做一些事情

无论如何,让我们这样做:

:- use_module(library(dcg/basics)).

% ---
% "Generate" direction; note that String may be bound to something
% in which case this clause also verifies whether generating "HH:MM"
% from time(H,M) indeed yields (whatever is denoted by) String.
% ---

process_time(time(H,M),String) :-
   integer(H),                            % Demand that H,M are valid integers inside limits
   integer(M),
   between(0,23,H),
   between(0,59,M),
   !,                                     % Guard passed, commit to this code branch
   phrase(time_g(H,M),Chars,[]),          % Build Codes from time/2 Term
   string_chars(String,Chars).            % Merge Codes into a string, unify with String

% ---
% "Parse" direction. 
% ---

process_time(time(H,M),String) :-
   string(String),                        % Demand that String be a valid string; no demands on H,M  
   !,                                     % Guard passed, commit to this code branch
   string_chars(String,Chars),            % Explode String into characters
   phrase(time_p(H,M),Chars,[]).          % Parse "Codes" into H and M

% ---
% "Generate" DCG
% ---
   
time_g(H,M) --> hour_g(H), [':'], minute_g(M).
hour_g(H)   --> { divmod(H,10,V1,V2), digit_int(D1,V1), digit_int(D2,V2) }, digit(D1), digit(D2).
minute_g(M) --> { divmod(M,10,V1,V2), digit_int(D1,V1), digit_int(D2,V2) }, digit(D1), digit(D2).

% ---
% "Parse" DCG
% ---
   
time_p(H,M) --> hour_p(H), [':'], minute_p(M).
hour_p(H)   --> digit(D1), digit(D2), { digit_int(D1,V1), digit_int(D2,V2), H is V1*10+V2, between(0,23,H) }.
minute_p(M) --> digit(D1), digit(D2), { digit_int(D1,V1), digit_int(D2,V2), M is V1*10+V2, between(0,59,M) }.   
   
% ---
% Do I really have to code this? Oh well!
% ---

digit_int('0',0).
digit_int('1',1).
digit_int('2',2).
digit_int('3',3).
digit_int('4',4).
digit_int('5',5).
digit_int('6',6).
digit_int('7',7).
digit_int('8',8).
digit_int('9',9).

% ---
% Let's add plunit tests!
% ---

:- begin_tests(hhmm).

test("parse 1",    true(T == time(0,0)))   :- process_time(T,"00:00").
test("parse 2",    true(T == time(12,13))) :- process_time(T,"12:13").
test("parse 1",    true(T == time(23,59))) :- process_time(T,"23:59").
test("generate",   true(S == "12:13"))     :- process_time(time(12,13),S).
test("verify",     true)                   :- process_time(time(12,13),"12:13").
test("complete",   true(H == 12))          :- process_time(time(H,13),"12:13").

test("bad parse",    fail)                 :- process_time(_,"66:66").
test("bad generate", fail)                 :- process_time(time(66,66),_).

:- end_tests(hhmm).
这是很多代码

它有用吗

?- run_tests.
% PL-Unit: hhmm ........ done
% All 8 tests passed
true.

考虑到模式的简单性,DCG可能被认为是过火了,但实际上它为我们提供了一个很容易访问原子成分的途径,我们可以将这些成分输入到一些声明性的算术库中。比如说

:-模块H_mm_bi, [hh_mm_bi/2 ,hh_mm_bi//1 ]. :-使用_modulelibrarydcg/basics。 :-使用_modulelibraryclpfd。 hh_-mm_钻头,S:-短语hh_-mm_钻头,S。 比蒂姆,M->n2H,23,:,n2M,59。 n2V,U->dA,dB,{V=A*10+B,V>=0,V=digitD,{V=D-0'0}。 一些测试

?-hh_-mm_位,`23:30`。 T=时间23,30。 ?-hh_-mm_位,`24:30`。 错误的 ?-短语h_mm_位,S。 T=time0,0, S=[48,48,58,48,48]; T=时间0,1, S=[48,48,58,48,49]; ... 编辑

libraryclpfd并不是我们对声明性算术的唯一选择。下面是另一个快照,使用,但它要求您使用?-pack_installclpBNR安装适当的包。完成后,可以使用另一个功能等同于上述解决方案的解决方案

:-模块H_mm_bnr, [hh_mm_bnr/2 ,hh_mm_bnr//1 ]. :-使用_modulelibrarydcg/basics。 :-使用_modulelibraryclpBNR。 H_mm_bnrT,S:-短语H_mm_bnrT,S。 hh_-mm_-bnrtimeH,M->n2H,23,:,n2M,59。 n2V,U->dA,dB,{V::integer0,U,{V==A*10+B}。 dV->digitD,{V==D-0'0}。 编辑

@DavidTonhofer现在删除的评论使我认为有一种更简单的方法可用,即将“发电”移到d//1:

:-模块H_mm, [hh_mm/2 ,hh_mm//1 ]. 嗯,S:-用词嗯,S。 hh_mmtimeH,M->n2H,23,:,n2M,59。 n2V,U->dA,dB,{V是A*10+B,V>=0,V=[C],{memberV[0,1,2,3,4,5,6,7,8,9],C是V+0'0}。
考虑到模式的简单性,DCG可能被认为是过火了,但实际上它为我们提供了一个容易访问原子成分的途径,我们可以将这些成分输入到一些声明性的算术库中

:-模块H_mm_bi, [hh_mm_bi/2 ,hh_mm_bi//1 ]. :-使用_modulelibrarydcg/basics。 :-使用_modulelibraryclpfd。 hh_-mm_钻头,S:-短语hh_-mm_钻头,S。 比蒂姆,M->n2H,23,:,n2M,59。 n2V,U->dA,dB,{V=A*10+B,V>=0,V=digitD,{V=D-0'0}。 一些测试

?-hh_-mm_位,`23:30`。 T=时间2 3, 30. ?-hh_-mm_位,`24:30`。 错误的 ?-短语h_mm_位,S。 T=time0,0, S=[48,48,58,48,48]; T=时间0,1, S=[48,48,58,48,49]; ... 编辑

libraryclpfd并不是我们对声明性算术的唯一选择。这是另一个镜头,使用,但它要求您安装适当的软件包,使用?-pack\u installclpBNR。完成此操作后,可以使用另一个功能等同于上述解决方案的解决方案

:-模块H_mm_bnr, [hh_mm_bnr/2 ,hh_mm_bnr//1 ]. :-使用_modulelibrarydcg/basics。 :-使用_modulelibraryclpBNR。 H_mm_bnrT,S:-短语H_mm_bnrT,S。 hh_-mm_-bnrtimeH,M->n2H,23,:,n2M,59。 n2V,U->dA,dB,{V::integer0,U,{V==A*10+B}。 dV->digitD,{V==D-0'0}。 编辑

@DavidTonhofer现在删除的评论使我认为有一种更简单的方法可用,即将“发电”移到d//1:

:-模块H_mm, [hh_mm/2 ,hh_mm//1 ]. 嗯,S:-用词嗯,S。 hh_mmtimeH,M->n2H,23,:,n2M,59。 n2V,U->dA,dB,{V是A*10+B,V>=0,V=[C],{memberV[0,1,2,3,4,5,6,7,8,9],C是V+0'0}。
另一个答案是,避免DCG对于这项任务来说是过分的。或者更确切地说,这里涉及的两个独立的任务:不是每个关系都可以用一个Prolog谓词表示,特别是不是像SWI Prolog的字符串这样具有额外逻辑性的东西上的每个关系

下面是其中一项任务的解决方案,计算代码重命名时的字符串:

time_string_(time(H,M), String) :-
    number_string(H,Hour),
    number_string(M,Min),
    string_concat(Hour,":",S),
    string_concat(S,Min,String).
例如:

?- time_string_(time(11, 59), String).
String = "11:59".
?- string_time_("11:59", Time).
Time = time(11, 59).
下面是相反转换的简单实现:

string_time_(String, time(H, M)) :-
    split_string(String, ":", "", [Hour, Minute]),
    number_string(H, Hour),
    number_string(M, Minute).
例如:

?- time_string_(time(11, 59), String).
String = "11:59".
?- string_time_("11:59", Time).
Time = time(11, 59).
下面是一个谓词,它根据已知的参数选择要使用的转换。确切的条件将取决于应用程序中可能出现的情况,但如果字符串确实是字符串,则可以合理地说,我们希望尝试解析它:

time_string(Time, String) :-
    (   string(String)
    ->  % Try to parse the existing string.
        string_time_(String, Time)
    ;   % Hope that Time is a valid time term.
        time_string_(Time, String) ).
这将从两个方面进行翻译:

?- time_string(time(11, 59), String).
String = "11:59".

?- time_string(Time, "11:59").
Time = time(11, 59).

另一个答案是,避免DCG对于这项任务来说是过分的。或者更确切地说,这里涉及的两个独立的任务:不是每个关系都可以用一个Prolog谓词表示,特别是不是像SWI Prolog的字符串这样具有额外逻辑性的东西上的每个关系

下面是其中一项任务的解决方案,计算代码重命名时的字符串:

time_string_(time(H,M), String) :-
    number_string(H,Hour),
    number_string(M,Min),
    string_concat(Hour,":",S),
    string_concat(S,Min,String).
例如:

?- time_string_(time(11, 59), String).
String = "11:59".
?- string_time_("11:59", Time).
Time = time(11, 59).
下面是相反转换的简单实现:

string_time_(String, time(H, M)) :-
    split_string(String, ":", "", [Hour, Minute]),
    number_string(H, Hour),
    number_string(M, Minute).
例如:

?- time_string_(time(11, 59), String).
String = "11:59".
?- string_time_("11:59", Time).
Time = time(11, 59).
下面是一个谓词,它根据已知的参数选择要使用的转换。确切的条件将取决于应用程序中可能出现的情况,但如果字符串确实是字符串,则可以合理地说,我们希望尝试解析它:

time_string(Time, String) :-
    (   string(String)
    ->  % Try to parse the existing string.
        string_time_(String, Time)
    ;   % Hope that Time is a valid time term.
        time_string_(Time, String) ).
这将从两个方面进行翻译:

?- time_string(time(11, 59), String).
String = "11:59".

?- time_string(Time, "11:59").
Time = time(11, 59).

CLP给表中带来了如此多的东西,这真是太神奇了。第三个解决方案很酷。你可以看到这样一个折衷:让代码不通过状态空间多项式或带有定制代码段的线性成本在确定性路径上搜索,而让代码搜索潜在的指数成本或更高的多项式成本,但使用通用代码。我删除了注释,因为我错了说解析而不是生成成员/2,你也可以使用0,9,V之间的值,非常简洁!CLP给表格带来了这么多,这太神奇了。很酷的第三个解决方案。你可以看到,通过状态空间多项式或线性成本,通过定制代码段,保持代码不在确定性路径上搜索,还是让代码搜索潜在的Pontial cost或更高的多项式cost,但使用泛型代码。我删除了注释,因为我错误地说解析而不是生成成员/2。为了非常简洁,还可以使用0,9,V之间的值!禁止对前缀0进行一些调整,但需要12*60次尝试来解析11:59。在我的系统中,调用time11,59需要.0000005秒conds.在修复前缀0时禁止进行某些调整,但需要12*60次尝试来解析11:59。在我的系统上,调用time11,59需要.000000 5秒。