问题描述
使用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
如果我不必编写全新的谓词来回答此查询,那将是非常不错的。有办法吗?
解决方法
好吧,从结构化术语time(H,M)
到字符串String
比从非结构化String
到术语time(H,M)
容易。
您的谓词沿“世代”方向工作。
对于另一个方向,您想解析 String
。在这种情况下,这在计算上很容易,并且无需搜索/回溯就可以完成,这很不错!
使用Prolog的"Definite Clause Grammar"语法“只是”一种编写处理“事物列表”的谓词的好方法。在这种情况下,填充列表是字符列表(长度为1的原子)。 (有关SWI-Prolog中的相关页面,请参见here)
如果运气好的话,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),59,!,% Guard passed,commit to this code branch
phrase(time_g(H,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,String) :-
string(String),% Demand that String be a valid string; no demands on H,M
!,commit to this code branch
string_chars(String,Chars),% Explode String into characters
phrase(time_p(H,[]). % 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,digit(D2).
% ---
% "Parse" DCG
% ---
time_p(H,M) --> hour_p(H),minute_p(M).
hour_p(H) --> digit(D1),digit(D2),{ digit_int(D1,H is V1*10+V2,H) }.
minute_p(M) --> digit(D1),M is V1*10+V2,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,"12:13").
test("complete",true(H == 12)) :- process_time(time(H,"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可能被视为过高的杀伤力,但实际上,它使我们能够轻松访问可输入某些声明性算术库的原子成分。例如
:- module(hh_mm_bi,[hh_mm_bi/2,hh_mm_bi//1
]).
:- use_module(library(dcg/basics)).
:- use_module(library(clpfd)).
hh_mm_bi(T,S) :- phrase(hh_mm_bi(T),S).
hh_mm_bi(time(H,M)) --> n2(H,23),":",n2(M,59).
n2(V,U) --> d(A),d(B),{V#=A*10+B,V#>=0,V#=<U}.
d(V) --> digit(D),{V#=D-0'0}.
一些测试
?- hh_mm_bi(T,`23:30`).
T = time(23,30).
?- hh_mm_bi(T,`24:30`).
false.
?- phrase(hh_mm_bi(T),S).
T = time(0,0),S = [48,48,58,48] ;
T = time(0,1),49] ;
...
编辑
library(clpfd)不是声明式算术的唯一选择。这是使用library(clpBNR)
的另一张照片,但是它要求您使用?- pack_install(clpBNR).
安装相应的软件包。完成此操作后,可以得到与上述功能等效的另一种解决方案
:- module(hh_mm_bnr,[hh_mm_bnr/2,hh_mm_bnr//1
]).
:- use_module(library(dcg/basics)).
:- use_module(library(clpBNR)).
hh_mm_bnr(T,S) :- phrase(hh_mm_bnr(T),S).
hh_mm_bnr(time(H,U) --> d(A),{V::integer(0,U),{V==A*10+B}}.
d(V) --> digit(D),{{V==D-0'0}}.
编辑
@DavidTonhofer的评论(现已删除)使我认为可以使用更简单的方法,将“发电能力”移至d // 1:
:- module(hh_mm,[hh_mm/2,hh_mm//1
]).
hh_mm(T,S) :- phrase(hh_mm(T),S).
hh_mm(time(H,U) --> d(A),{ V is A*10+B,V>=0,V=<U }.
d(V) --> [C],{ member(V,[0,1,2,3,4,5,6,7,8,9]),C is V+0'0 }.
,
time_string(time(H,String)
:-
hour(H),minute(M),number_string(H,Hs),number_string(M,Ms),string_concat(Hs,S),string_concat(S,Ms,String)
.
hour(H) :- between(0,11,H) .
minute(M) :- between(0,M) .
/*
?- time_string(time(10,30),B).
B = "10:30".
?- time_string(time(H,"10:30").
H = 10,M = 30 ;
false.
?- time_string(time(H,S).
H = M,M = 0,S = "0:0" ;
H = 0,M = 1,S = "0:1" ;
H = 0,M = 2,S = "0:2" ;
H = 0,M = 3,S = "0:3" %etc.
*/
,
另一个答案,避免将DCG视为此任务的过分杀伤力。或更确切地说,这里涉及两个独立的任务:并非每个关系都可以在单个Prolog谓词中表达,尤其是并非每个关系都像SWI-Prolog的字符串一样具有逻辑性。
因此,这是其中一项任务的解决方案,它可以不时计算字符串(这是您的代码重命名):
time_string_(time(H,String) :-
number_string(H,Hour),Min),string_concat(Hour,Min,String).
例如:
?- time_string_(time(11,59),String).
String = "11:59".
这是相反转换的简单实现:
string_time_(String,time(H,M)) :-
split_string(String,"",[Hour,Minute]),Minute).
例如:
?- 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,String).
String = "11:59".
?- time_string(Time,"11:59").
Time = time(11,59).