跳轉到內容

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:數學、函式和等式

華夏公益教科書