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).