Prolog/列表
任何语言都需要一种方法来处理对象集合,prolog也不例外。prolog 中的列表可以像这样
[a, b, c, d, e]
方括号是列表的开头和结尾,逗号分隔各个元素。这里所有元素都是原子,但列表可以包含各种元素,并且不同类型的元素可以出现在同一个列表中。以下是有效列表的示例
[a, A, B, C, D] [p(A,V), p(a, V)] [[a1,a2,a3],[b1,b2,b3],[c1,c2,c3]] [[A,B], [B,C], quu([a,b,c],[a,b,d,e,f],_), c, m, D]
大多数 prolog 代码不会像这些示例那样显式定义列表,而是处理任何长度的列表,以及许多可能的元素。要处理不知道其内部内容或长度的列表,可以使用竖线表示法
[Head|Tail]
在此表示法中,变量 Head 代表列表中最左边的元素,变量 Tail 代表作为另一个列表表示的列表的其余部分。头部可以是任何东西,从谓词到另一个列表,但尾部始终是另一个列表。
提供了一些默认库规则,例如length、reverse、append(此外,这些规则也可以轻松定义,如本页底部所示)。要查看这些规则的工作原理,请尝试以下代码
?- length([1, 3, 6], What).
Prolog 将回答 3。
?- append([a], b, Z).
?- append([a], [b], Z).
观察这里的区别。
?- append(X, [1, 2], [1, 1, 2]).
?- reverse([1,2], What).
以下程序显示了如何使用它。
listsplit([H|T], H, T).
这里,[H|T] 是一个列表,H 是头部,T 是尾部。对于查询
?- listsplit([a,b,c,d,e], a, [b,c,d,e]).
Prolog 将回答 yes。对于查询
?- listsplit([a,b,c,d,e], A, B).
Prolog 将回答
A = a B = [b, c, d, e] ;
该程序甚至可以用来“创建”一个列表
?- listsplit(List, a, [b,c,d,e]). List = [a, b, c, d, e] ;
以下是列表的示例,以及它们产生的头部和尾部
列表 | 头部 | 尾部 |
---|---|---|
[a,b,c,d,e] |
a |
[b,c,d,e] |
[a] |
a |
[] (an empty list) |
[[a,b,c],a,b,[a,b]] |
[a,b,c] |
[a,b,[a,b]] |
请注意,空列表 [ ] 不能被拆分,因此不会与 [H|T] 统一。
拆分列表可以用两个以上的头部完成
[H1, H2, H3|Tail]
这将把列表拆分为前三个元素和列表的其余部分。但是请注意,如果列表的元素少于三个,这将失败。
现在考虑以下程序
last([Elem], Elem). last([_|Tail], Elem) :- last(Tail, Elem).
这个关系定义了列表的最后一个元素。它可以用作查找列表最后一个元素的程序
?- last([a,b,c,d,e],X). X = e
首先,是停止谓词,它表示只有一个元素的列表的最后一个元素是该元素。第二行表示列表 [_|Tail] 的最后一个元素 (Elem) 是其尾部 (Tail) 的最后一个元素。由于我们不关心头部,所以我们使用匿名变量 _ 来表示它。
通常,您将使用这些列表操作的内置谓词,而不是自己编写它们。内置谓词由您的 prolog 实现定义,但可以在任何程序中使用。这些实现在这里显示是为了说明如何修改列表。
Member 是一个标准的 prolog 内置 谓词。您像这样使用它
member(Element, List).
其中 List 是任何 prolog 列表,Element 是该列表中的任何元素。例如,以下内容成功(返回“是”)
?- member(a, [a, b, c]). ?- member(b, [a, b, c]). ?- member(c, [a, b, c]).
此查询
?- member(Element, [a, b, c]).
将为 Element 返回以下值
Element = a; Element = b; Element = c;
member 谓词定义如下
member(X, [X|_]). % member(X, [Head|Tail]) is true if X = Head % that is, if X is the head of the list member(X, [_|Tail]) :- % or if X is a member of Tail, member(X, Tail). % ie. if member(X, Tail) is true.
内置谓词 append/3 将一个列表附加到另一个列表的后面,换句话说,它连接两个列表。它像这样使用
append(Xs, Ys, Zs)
其中 Zs 是 Ys 附加到 Xs。以下内容成功
append([a, b, c], [1, 2, 3], [a, b, c, 1, 2, 3]). append([], [a, b, c], [a, b, c]). append([A, B, C], [1, 2, 3], [A, B, C, 1, 2, 3]).
您可以使用它来附加两个列表
?- append([a, b, c], [d, e, f], Result). Result = [a, b, c, d, e, f]
或者将列表拆分为左右部分,
?- append(ListLeft, ListRight, [a, b, c]). ListLeft = [] ListRight = [a, b, c] ; ListLeft = [a] ListRight = [b, c] ; ListLeft = [a, b] ListRight = [c] ; ListLeft = [a, b, c] ListRight = [] ; No
您甚至可以将它与三个变量一起使用。(自己尝试一下,看看结果是什么)。
append 谓词可以这样定义
append([], Ys, Ys). append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
第一行只是将最后两个列表统一起来,它对类似 append([], [a,b,c], [a,b,c]). 或 append([], X, [e]). 的查询成功,在这种情况下,它将绑定 X = [e]。第二行(递归子句)声明,给定 append(A,B,C),A 的头部等于 C 的头部,并且将 A 的尾部与 B 相附加,得到 C 的尾部。
由于 Prolog 的非确定性,append/3 有许多用途。它可以用作实现前面定义的 last/2 的另一种方法。
last(List, Last) :- append(_, [Last], List).
还有更多定义是可能的,
split(List, Pivot, Left, Right) :- append(Left, [Pivot|Right], List).
?- split([o,o,x,e,e,e], x, L, R). L = [o, o], R = [e, e, e] ;
?- split(A, -, [o,o], [u,u,u]). A = [o, o, -, u, u, u].
我们将看看两种反转列表的方法,首先是简单的方法,就是不断地从头部取下元素并将其附加到末尾,
reverse([],[]). reverse([X|Xs],YsX) :- reverse(Xs,Ys), append(Ys,[X],YsX).
执行它意味着您一次又一次地遍历列表,每次都附加,可以创建一个更有效的版本,通过获取 append 的定义
append([], Ys, Ys). append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
稍微改变一下,
revappend([], Ys, Ys). revappend([X|Xs], Ys, Zs) :- revappend(Xs, [X|Ys], Zs).
然后,
reverse(Xs,Ys) :- revappend(Xs,[],Ys).
revappend 中使用的策略被称为累积参数。
(1) 内置谓词 length() 可以用来查找列表的长度。例如
?- length([a,b,95,[1,1,1,1]],X). X = 4 . ?- length([a,XYZ,59,1,1,1,1],X). X = 7 .
这个谓词是如何定义的?
(1) 就像你猜的那样,我们将使用递归。
len([], 0). len([_ | Tail], Length) :- len(Tail, Length1), Length is Length1 + 1,!.
另一个使用尾递归优化和 Prolog 算术(因此使用更少的堆栈空间)的解决方案
% 0 - Calling Rule cl(List, Out) :- call(List, 0 , Out). % 1 - Terminating condition call([], Count, Count). % 2 - Recursive rule call([H|T], Count, Out) :- Count1 is Count + 1, call(T, Count1, Out).