跳转到内容

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 代表作为另一个列表表示的列表的其余部分。头部可以是任何东西,从谓词到另一个列表,但尾部始终是另一个列表。

提供了一些默认库规则,例如lengthreverseappend(此外,这些规则也可以轻松定义,如本页底部所示)。要查看这些规则的工作原理,请尝试以下代码

 ?- 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) 的最后一个元素。由于我们不关心头部,所以我们使用匿名变量 _ 来表示它。

member 谓词

[编辑 | 编辑源代码]

通常,您将使用这些列表操作的内置谓词,而不是自己编写它们。内置谓词由您的 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 谓词

[编辑 | 编辑源代码]

内置谓词 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).

prev:变量 next:数学、函数和等式

华夏公益教科书