如何在SWI Prolog中复制预定义长度/2的行为?

如何在SWI Prolog中复制预定义长度/2的行为?,prolog,clpfd,Prolog,Clpfd,我试图复制标准长度/2谓词的行为。特别是,我希望谓词适用于有界参数和无界参数,如下例所示: % Case 1 ?- length(X, Y). X = [], Y = 0 ; X = [_G4326], Y = 1 ; X = [_G4326, _G4329], Y = 2 ; X = [_G4326, _G4329, _G4332], Y = 3 . % Case 2 ?- length([a,b,c], X). X = 3. % Case 3 ?- length(X, 4). X

我试图复制标准长度/2谓词的行为。特别是,我希望谓词适用于有界参数和无界参数,如下例所示:

% Case 1
?- length(X, Y).
X = [],
Y = 0 ;
X = [_G4326],
Y = 1 ;
X = [_G4326, _G4329],
Y = 2 ;
X = [_G4326, _G4329, _G4332],
Y = 3 .

% Case 2    
?- length([a,b,c], X).
X = 3.

% Case 3
?- length(X, 4).
X = [_G4314, _G4317, _G4320, _G4323].

% Case 4
?- length([a,b,c,d,e], 5).
true.
简单明了的实现:

my_length([], 0).
my_length([_|T], N) :- my_length(T, X), N is 1+X.
有一些问题。在案例3中,在生成正确答案后,它进入一个无限循环这个谓词可以转换为确定性谓词吗?还是以false停止的非确定性谓词

是的但使用红色切割。见:


一段时间后,我成功地编写了一组谓词,它们模仿内置length/2的行为。我的_len_tail是确定性的,在所有情况下都是正确的1-4可以简单些吗?

my_len_tail(List, Len) :- var(Len)->my_len_tailv(List, 0, Len);
                          my_len_tailnv(List, 0, Len).

my_len_tailv([], Acc, Acc).
my_len_tailv([_|T], Acc, Len) :-
    M is Acc+1,
    my_len_tailv(T, M, Len).

my_len_tailnv([], Acc, Acc) :- !. % green!
my_len_tailnv([_|T], Acc, Len) :-
    Acc<Len,
    M is Acc+1,
    my_len_tailnv(T, M, Len).
:-use_module(library(clpfd)).
my_len_clp(List, Len) :- my_len_clp(List, 0, Len).

my_len_clp([], Acc, Acc).
my_len_clp([_|T], Acc, Len) :-
    Acc#<Len,
    M is Acc+1,
    my_len_clp(T, M, Len).

可以使用CLP(FD)库中的
zcompare/3
对其进行修复。请看:

我对这个答案不是特别有信心,但我的想法是否定的,您必须做一些额外的工作,使Prolog在
长度/2
上做正确的事情,这是一个真正的耻辱,因为在最简单的演示中,它是一个非常好的“教程”谓词

我提交作为证据,并且。这两个都不是一个简洁、可爱的技巧,在我看来,它们都是通过测试参数然后根据实例化的参数将处理延迟到不同的内部函数来工作的


但我很想在这一点上犯错。例如,我经常想知道为什么写
member/2
这样容易,而写
length/2
这样难。Prolog不擅长算术,但它真的那么糟糕吗?希望其他人能给出更好的答案。

这适用于您的所有测试用例(但它有红色部分):


在SWI Prolog中,可以使用CLP(FD)的
zcompare/3
解决不确定性问题,它将不等式具体化为一个可用于索引的术语:

:- use_module(library(clpfd)).

my_length(Ls, L) :-
        zcompare(C, 0, L),
        my_length(Ls, C, 0, L).

my_length([], =, L, L).
my_length([_|Ls], <, L0, L) :-
        L1 #= L0 + 1,
        zcompare(C, L1, L),
        my_length(Ls, C, L1, L).
?- my_length(Ls, 3).
Ls = [_G356, _G420, _G484].

所有严肃的Prolog实现都附带CLP(FD),在这里使用它非常有意义。请您的供应商也实施
zcompare/3
,或者更好的替代方案(如果尚未提供)。

有关一组测试用例,请参阅和。还有许多奇怪的情况需要考虑。< /P> 用
var/nonvar
is/2
等定义
length/2
,并不是很简单,因为
(is)/2
和算术比较非常有限。也就是说,它们非常频繁地产生实例化错误,而不是相应地成功。只是为了说明这一点:使用定义
length\u sx/2
很简单

这个定义非常完美。它甚至在
length\u sx(L,L)
时失败。遗憾的是,后继算法没有得到有效的支持。也就是说,整数i需要O(i)空间,而不是人们所期望的O(logi)

我更喜欢的定义是:

length_fd([],0).
length_fd([_E|Es], L0) :-
   L0 #> 0,
   L1 #= L0-1,
   length_fd(Es, L1).
这是最直接的翻译。在已知长度的情况下,它是相当有效的,但在其他方面,它会减少显示后约束的开销。此外,还有这种不对称性:

?- length_fd(L,0+0).
false.

?- length_fd(L,0+1).
L = [_G919] ;
false.
然而,您使用
库(clpfd)
的定义尤其优雅高效,即使对于更复杂的情况也是如此。。它没有内置长度快

?- time(( length_fd(L,N),N=1000 )).
% 29,171,112 inferences, 4.110 CPU in 4.118 seconds (100% CPU, 7097691 Lips)
L = [_G67, _G98, _G123, _G159, _G195, _G231, _G267, _G303, _G339|...],
N = 1000 .

?- time(( my_len_clp(L,N),N=10000 )).
% 1,289,977 inferences, 0.288 CPU in 0.288 seconds (100% CPU, 4484310 Lips)
L = [_G67, _G79, _G82, _G85, _G88, _G91, _G94, _G97, _G100|...],
N = 10000 .

?- time(( length(L,N),N=10000 )).
% 30,003 inferences, 0.006 CPU in 0.006 seconds (100% CPU, 4685643 Lips)
L = [_G67, _G70, _G73, _G76, _G79, _G82, _G85, _G88, _G91|...],
N = 10000 .
。。。但是,它能够正确地处理约束:

?- N in 1..2, my_len_clp(L,N).
N = 1,
L = [_G1439] ;
N = 2,
L = [_G1439, _G1494] ;
false.

?- N in 1..2, length(L,N).
N = 1,
L = [_G1445] ;
N = 2,
L = [_G1445, _G1448] ;
*LOOPS*
(我试图编辑@false,但被拒绝)

my_len_tail/2
在生成列表时比buldin
length/2
更快(在推理次数和实际时间方面),但在1..2约束中存在
N问题

?- time(( my_len_tail(L,N),N=10000000 )).
% 20,000,002 inferences, 2.839 CPU in 3.093 seconds (92% CPU, 7044193 Lips)
L = [_G67, _G70, _G73, _G76, _G79, _G82, _G85, _G88, _G91|...],
N = 10000000 .

?- time(( length(L,N),N=10000000 )).
% 30,000,004 inferences, 3.557 CPU in 3.809 seconds (93% CPU, 8434495 Lips)
L = [_G67, _G70, _G73, _G76, _G79, _G82, _G85, _G88, _G91|...],
N = 10000000 .
实施

测试


如果有人能够提供一个解决方案而不必求助于供应商扩展或clp(fd),那将特别有趣。谢谢@DanielLyons的建议。我已经更新了我的帖子。但问题仍然存在。
my_lenu_clp/3的+1正好非常有效!在这里使用它是有意义的,但从教学上讲,了解一个人在没有它的情况下如何解决这类问题是很有趣的。我发现这个应用程序比文档条目更明确。CLP(FD)绝对有用!我已经对一个查询
我的长度(X,1)
进行了
跟踪/1
。呼叫树大约有30层深,有很多未定位的步骤。和往常一样,我们在速度/功率方面进行了权衡。在这种情况下,
L1#=L0+1
在某种程度上比
L1是L0+1
好吗?一般注意:在讲授涉及整数的谓词时,我认为教学上最好使用有限域约束,因为它们是可以在所有方向上使用的真实关系。如果没有约束,则必须引入非单调谓词来处理整数
L1#=L0+1
提供了一个比使用
is/2
更通用的谓词,请尝试使用两个版本的最通用查询
my_长度(Ls,C,L0,L)
。速度差很容易测量(在严肃的应用程序中通常可以忽略不计),并且您获得了很多通用性。上面关于
zcompare(C,0,L)
的一点说明是:这涉及到创建一个冻结的目标,然后立即执行。正是这样的例子让我觉得更喜欢一个不确定的解决方案。看起来,您的解决方案使用了与我的my_len_tail基本相同的想法:测试我们是否已完全实例化长度,如果已完全实例化,则在检查点的第一个选择后中止。但是在case
my_len([1,2,3],X)
PROLOG尝试使用第二个谓词统一3次(每次都失败)。为了解决这个问题,我在第一次统一时使用了“蹦床”来选择正确的长度函数。你能举一些例子说明在这个函数中,人们更喜欢地面/1而不是非地面/1吗?
nonvar/1
在这里可能更好,但是
integer/1
更好。第二个子句中应添加
N>0
?- N in 1..2, my_len_clp(L,N).
N = 1,
L = [_G1439] ;
N = 2,
L = [_G1439, _G1494] ;
false.

?- N in 1..2, length(L,N).
N = 1,
L = [_G1445] ;
N = 2,
L = [_G1445, _G1448] ;
*LOOPS*
?- time(( my_len_tail(L,N),N=10000000 )).
% 20,000,002 inferences, 2.839 CPU in 3.093 seconds (92% CPU, 7044193 Lips)
L = [_G67, _G70, _G73, _G76, _G79, _G82, _G85, _G88, _G91|...],
N = 10000000 .

?- time(( length(L,N),N=10000000 )).
% 30,000,004 inferences, 3.557 CPU in 3.809 seconds (93% CPU, 8434495 Lips)
L = [_G67, _G70, _G73, _G76, _G79, _G82, _G85, _G88, _G91|...],
N = 10000000 .
goal_expansion((_lhs_ =:= _rhs_),(when(ground(_rhs_),(_lhs_ is _rhs_))))  .

:- op(2'1,'yfx','list')  .

_list_ list [size:_size_] :-
_list_ list [size:_size_,shrink:_shrink_] ,
_list_ list [size:_size_,shrink:_shrink_,size:_SIZE_]  .

_list_ list [size:0,shrink:false]  .

_list_ list [size:_size_,shrink:true] :-
when(ground(_size_),(_size_ > 0))  .

[] list [size:0,shrink:false,size:0] .

[_car_|_cdr_] list [size:_size_,shrink:true,size:_SIZE_] :-
(_SIZE_ =:= _size_ - 1) ,
(_size_ =:= _SIZE_ + 1) ,
_cdr_ list [size:_SIZE_]  .
/*
   ?- L list Z .
L = [],
Z = [size:0] ? ;
L = [_A],
Z = [size:1] ? ;
L = [_A,_B],
Z = [size:2] ? ;
L = [_A,_B,_C],
Z = [size:3] ?
yes

   ?- L list [size:0] .
L = [] ? ;
no
   ?- L list [size:1] .
L = [_A] ? ;
no
   ?- L list [size:2] .
L = [_A,_B] ? ;
no

   ?- [] list [size:S] .
S = 0 ? ;
no
   ?- [a] list [size:S] .
S = 1 ? ;
no
   ?- [a,b] list [size:S] .
S = 2 ? ;
no
   ?- [a,b,c] list [size:S] .
S = 3 ? ;
no
   ?- 
*/