理解差分列表的关键是理解它们在表示列表的嵌套复合术语层面上是什么。通常我们看到的是这样的一个列表:
[a, b, c]
这是一个有三个元素的列表。使用点作为列表构造器的嵌套术语,./2
,以及原子 []
作为空列表,将是:
.(a, .(b, .(c, [])))
在这里很重要的一点是,列表函子是一个具有两个参数的复合术语:元素和列表的其余部分。空列表是一个原子,可以非正式地看作是一个没有参数的元数为0的复合术语。
现在,这是一个具有三个元素的列表,其中最后一个元素是一个自由变量:
[a, b, Last]
这与以下内容相同:
.(a, .(b, .(Last, [])))
另一方面,这是一个包含两个元素和自由变量作为列表其余部分(或称为 tail)的列表:
[a, b|Tail]
这与以下内容相同:
.(a, .(b, Tail))
你看到了如何使用.(a, .(b, .(Last, [])))
和.(a, .(b, Tail))
是不同的吗?
从顶层尝试这个(我正在使用需要--traditional
标志将./2
作为列表项处理的SWI-Prolog 7):
$ 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]
在此联合中的确切作用。
在使用差异列表的谓词中,您需要两个参数或者有时是一个对来保存不完整的列表及其尾部,如下所示:
foo([a,b|Tail], Tail).
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).
将列表的后面
与第三个参数统一:
?- 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, []))
现在你看到了吗?
read_pending_input/3
),所以不幸的是,差异列表无法完全避免,至少在没有纯输入/输出的完整实现的情况下。 - user1812457