我正在尝试理解Prolog中的差异列表,但我正在努力实际正确地实现一个,每次我尝试这样做,我得到一个列表列表,但这不是我想要的。我正在尝试实现一个追加谓词,但到目前为止运气不佳。几次尝试都没有用。
app(X, Y, Z) :- Z = [X|Y].
?- app([a,b,c], [z], Z).
Z = [[a,b,c],z].
OR
app(X, Y, Z) :- Z = [X|Hole], Hole = Y.
与第一个结果相同(它们看起来基本相同)。我在一本有效的书中有一个例子(虽然它不是谓词),我不明白其中的区别。 X
被实例化为正确答案[a,b,c,z]
,与我的第二个例子有什么不同?
X = [a,b,c|Y], Y = [z].
我错过了什么?感谢。
答案 0 :(得分:22)
理解差异列表的关键是理解它们在表示列表的嵌套复合词的级别上的含义。通常,我们会看到类似的列表:
[a, b, c]
现在这是一个包含三个元素的列表。使用点作为列表仿函数./2
和原子[]
作为空列表的完全相同的嵌套术语将是:
.(a, .(b, .(c, [])))
这里重要的是列表仿函数是一个带有两个参数的复合词:元素和列表的其余部分。空列表是一个原子,非正式地,它可以被视为具有arity 0的复合词,即没有参数。
现在,这是一个包含三个元素的列表,其中最后一个元素是一个自由变量:
[a, b, Last]
与:
相同.(a, .(b, .(Last, [])))
另一方面,这是一个列表,其中包含两个元素和一个自由变量作为列表的其余部分,或尾部:
[a, b|Tail]
与:
相同.(a, .(b, Tail))
您是否了解.(a, .(b, .(Last, [])))
与.(a, .(b, Tail))
的不同之处?
从顶级尝试此操作(我使用SWI-Prolog 7,需要--traditional
标志将./2
视为列表术语):
$ swipl --traditional
Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.1.26)
Copyright (c) 1990-2014 University of Amsterdam, VU Amsterdam
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
Please visit http://www.swi-prolog.org for details.
For help, use ?- help(Topic). or ?- apropos(Word).
?- [a, b, Last] = [a, b|Tail].
Tail = [Last].
?- .(a, .(b, .(Last, []))) = .(a, .(b, Tail)).
Tail = [Last].
现在,“差异列表”就是这样一个列表:[a, b|Tail]
,与.(a, .(b, Tail))
相同,您可以在其中保留保留尾部的变量Tail
。在将Tail
实例化为正确的列表之前,这是不一个正确的列表!
?- L = [a, b|Tail], is_list(L).
false.
?- L = [a, b|Tail], Tail = [c,d,e], is_list(L).
L = [a, b, c, d, e],
Tail = [c, d, e].
您可以查看之前的查询,了解Tail = [c, d, e]
在这种情况下的确切行为。
在使用差异列表的谓词中,您需要两个参数,或者有时候,对,以保留不完整列表及其尾部,如下所示:
% using two arguments
foo([a,b|Tail], Tail).
% using a pair
foo([a,b|Tail]-Tail).
第一个foo/2
有两个参数,第二个有一个参数,它是一个“对”。现代Prolog代码似乎更喜欢一对的两个参数,但你经常在教科书和教程中看到这对。
要附加,或app/3
:当您使用差异列表时,需要额外的参数(或一对),以便您可以访问列表的尾部正在处理。如果你只有前面列表的尾部,你仍然可以写一个只有三个参数的附加,因为只需要将第一个列表的尾部与第二个列表统一起来:
% app(List1, Tail1, List2)
app(List1, Tail1, List2) :- Tail1 = List2.
或直接在头脑中统一:
app(_L1, L2, L2).
?- L1 = [a,b|Tail], app(L1, Tail, [c]).
L1 = [a, b, c],
Tail = [c].
这与@Wouter提供的链接完全相同。
如果你有两个列表的尾部,你将用第二个列表替换第一个列表的尾部,并保留第二个列表的尾部。
app(List1, Tail1, List2, Tail2) :- Tail1 = List2.
再一次,你可以在头脑中完成统一。
修改强>:
列表已经完全实例化后,您无法创建“漏洞”。您将如何从.(a, .(b, .(c, [])))
转到此.(a, .(b, .(c, Tail)))
?除了遍历列表并将[]
替换为Tail
之外,您不能这样做,但这正是普通append/3
所做的。尝试:
?- L = [a,b,c,d], append(L, Back, Front), Back = [x,y,z].
L = [a, b, c, d],
Back = [x, y, z],
Front = [a, b, c, d, x, y, z].
或者,如果您将diflist_append/3
定义为:
diflist_append(Front, Back, Back).
将Back
列表与第三个参数统一起来:
?- L = [a,b,c,d], append(L, Back, Front), diflist_append(Front, Back, [x,y,z]).
L = [a, b, c, d],
Back = [x, y, z],
Front = [a, b, c, d, x, y, z].
至于您的示例X = [a,b,c], Y = [X|Z], Z = [z]
,这与:
X = .(a, .(b, .(c, []))),
Y = .(X, Z), % Y = .(.(a, .(b, .(c, []))), Z)
Z = [z] % Y = .(.(a, .(b, .(c, []))), .(z, []))
所以你现在看到了吗?
答案 1 :(得分:7)
Paul Brna has explained this very well.他在差异列表版本的附加中使用变量OpenList#
和Hole#
:
difference_append(OpenList1-Hole1, Hole1-Hole2, OpenList1-Hole2).
使用示例:
?- difference_append([a,b,c|H1]-H1, [d,e,f|H2]-H2, L).
H1 = [d, e, f|H2],
L = [a, b, c, d, e, f|H2]-H2.