Prolog 序言差异表

Prolog 序言差异表,prolog,difference-lists,Prolog,Difference Lists,考虑以下程序,一个使用差异列表,另一个不使用: reverse1(List1,R) :- rev1(List1, R-[]). rev1([], A-A). rev1([H|T], C-A) :-rev1(T, C - [H|A]). reverse2(List1,R) :- rev2(List1, R, []). rev2([], A, A). rev2([H|T], C, A) :- rev2(T, C, [H|A]). 既然两者都做相同的事情,那么使用差异列表的好处是什么?在给出的示例

考虑以下程序,一个使用差异列表,另一个不使用:

reverse1(List1,R) :- rev1(List1, R-[]).
rev1([], A-A).
rev1([H|T], C-A) :-rev1(T, C - [H|A]).

reverse2(List1,R) :- rev2(List1, R, []).
rev2([], A, A).
rev2([H|T], C, A) :- rev2(T, C, [H|A]).

既然两者都做相同的事情,那么使用差异列表的好处是什么?

在给出的示例中,
reverse1
并没有使用真正的差异列表,而只是
reverse2
的不同表示。它们都以相同的方式使用相同的变量
reverse
使用
-
函子附加它们,并且
reverse2
将它们作为单独的参数进行维护。但这就是两者的不同之处。算法行为是相同的

真正的差异列表是一个列表结构,其中有一个“洞”,比如
X-T
,其中
T
没有被实例化(也许直到稍后的时间点),并且
X
包含
T
(例如,
[A,b,c | T]-T
)。其中的
-
函子将“暴露的”未实例化变量与包含该变量的结构相关联

一个流行且简单的示例是使用差异列表实现
append
append
的传统递归实现可能如下所示:

append([], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
非常简单,执行时间与第一个列表的长度成正比

使用差异列表,您可以实现如下所示的
追加

append(A-B, B-C, A-C).
这就是使用差异列表附加列表所需的全部内容。没有递归或其他任何东西。执行时间是
O(1)
与列表的长度无关。下面是一个执行示例:

append([1,2,3|T]-T, [4,5,6|W]-W, DL).
收益率:

DL = [1,2,3,4,5,6|W]-W
T = [4,5,6|W]
您可以通过在最后一个参数中使用空列表“填充”孔来获得具体答案:

append([1,2,3|T]-T, [4,5,6|W]-W, L-[]).
你会得到:

L = [1,2,3,4,5,6]
T = [4,5,6]
W = []

您在示例中看到的不是一个差异列表。比较一下。差异列表使用的技巧是将尾部保持为已知的变量,并且可以直接更改。因此,它允许在列表中附加常量时间。但这不是你在你的例子中所做的


看看您的示例,
rev1
实际上只是使用了
-
作为分隔符,就好像它是一个逗号一样。我认为唯一的区别是在
rev2
中,prolog解释器有机会以一种提高性能的方式索引规则。不过,我不确定在这种情况下是否会有什么不同。从美学角度讲,第二种解决方案对我来说也更干净。

我从未见过使用“上下文外”的差异列表,主要上下文是DCGs实现

这是一个基于DCG的反面(我自己写的,但你也可以在Christian链接的页面上找到)

列出它可以证明您的reverse2几乎相同:

?- listing(rev3).
stackoverflow:rev3([], A, A).
stackoverflow:rev3([D|A], B, E) :-
    rev3(A, B, C),
    C=[D|E].
所有这些定义都有一个共同的问题:它们在“向后”模式下使用时,在第一个解决方案后回溯时循环:

?- reverse1(R,[a,b,c]).
R = [c, b, a] ; (^C here)
Action (h for help) ? abort
% Execution Aborted
接下来,我们来看看正确、高效的库实现:

?- listing(reverse).
lists:reverse(A, B) :-
    reverse(A, [], B, B).

lists:reverse([], A, A, []).
lists:reverse([B|A], C, D, [_|E]) :-
    reverse(A, [B|C], D, E).
这里没有不同的列表


那么,关于你的问题,我想说差异列表的唯一好处是更好地理解Prolog…

事实上,
列表的第三个和第四个参数:reverse/4
形成了一个差异列表,它开始时是空的(
B-B
),并且在每一步上都会延长一个非约束元素,因此,第四个arg表示第三个arg的渐进尾部。:)感谢大家及时的回答。为什么append/3没有定义为:
append(I-M,M,I)。
?我对
I-M
的理解是,它意味着
I是一个列表,谁的尾巴是M
。你的定义和这个更复杂。为什么?@Rolf在Prolog中,头
H
M
的列表使用
函子表示为
。(H,T)
,或者更常见的语法是
[H | T]
I-M
对于列表没有语义含义。它只是一个二元函子,
-
,有两个参数,
I
M
,或者可以写成,
'-'(I,M)
。在上面的回答中,我可以很容易地使用另一个定义为二进制运算符的二元函子,例如
+
,而不是
-
,并且行为是相同的。
?- listing(reverse).
lists:reverse(A, B) :-
    reverse(A, [], B, B).

lists:reverse([], A, A, []).
lists:reverse([B|A], C, D, [_|E]) :-
    reverse(A, [B|C], D, E).