簡體   English   中英

在Prolog中反轉列表

[英]Reversing a List in Prolog

我已完成編程課的作業。 我應該創建一個反轉列表的Prolog程序。 但是,我很難理解為什么它的確有效。

%1. reverse a list
%[a,b,c]->[c,b,a]

%reverse(list, rev_List).
reverse([],[]).  %reverse of empty is empty - base case
reverse([H|T], RevList):-
    reverse(T, RevT), conc(RevT, [H], RevList).  %concatenation

在這種情況下,RevT究竟是什么? 我知道它應該代表T的反向或給定列表的其余部分,但是我沒有看到它如何具有任何值,因為我沒有將它分配給任何東西。 它是否與RevList具有相同的用途,但對於每個遞歸調用?

另外,為什么我必須在我的conc()函數調用中使用[H]而不是H? H不是指列表的頭部(例如:[H])? 或者它只是引用列表頭部的項目(只是H)?

請幫我解決這個問題。 我正在努力理解這種編程背后的邏輯。

您的解決方案解釋說:如果我們反轉空列表,我們將獲得空列表。 如果我們反轉列表[H | T],我們最終得到通過反轉T並與[H]連接得到的列表。 要查看遞歸子句是否正確,請考慮列表[a,b,c,d]。 如果我們反轉這個列表的尾部,我們得到[d,c,b]。 將其與[a]連接得[d,c,b,a],這與[a,b,c,d]相反

另一個反向解決方

 reverse([],Z,Z).

 reverse([H|T],Z,Acc) :- reverse(T,Z,[H|Acc]).

呼叫:

?- reverse([a,b,c],X,[]).

欲了解更多信息,請閱讀: http//www.learnprolognow.org/lpnpage.php?pagetype = html&pageid = lll -htmlse25

Prolog的列表是簡單的數據結構: ./2

  • 空列表是atom []
  • 一個元素[a]列表實際上是這個結構: .(a,[])
  • 兩個元素的列表[a,b]實際上是這種結構: .(a,.(b,[]))
  • 一個包含三個元素的列表[a,b,c]實際上就是這樣的結構: .(a,.(b,.(c,[])))
  • 等等。

方括號表示法是一種語法糖,可以讓你不必花費你的生命打字括號。 更不用說它在眼睛上更容易了。

由此,我們得到列表頭部的概念(最外層./2結構中的數據)和列表的尾部 (子列表包含在最外層的./2數據結構中)。

這基本上是您在C中看到的經典單鏈表的相同數據結構:

struct list_node
{
  char payload ;
  struct list_node *next ;
}

其中next指針是NULL或其他列表結構。

那么,從那以后,我們得到了反向/ 2的簡單[天真]實現:

reverse( [] , []    ) .  % the empty list is already reversed.
reverse[ [X] , [X]  ) .  % a list of 1 item is already reversed. This special case is, strictly speaking, optional, as it will be handled by the general case.
reverse( [X|Xs] , R ) :- % The general case, a list of length >= 1 , is reversed by
  reverse(Xs,T) ,        % - reversing its tail, and
  append( T , [X] , R )  % - appending its head to the now-reversed tail
  .                      %

該算法可用於在更傳統的編程語言中反轉單鏈表。

但是,這種算法效率不高:對於初學者來說,它表現出O(n 2 )行為。 它也不是尾遞歸,這意味着足夠長度的列表將導致堆棧溢出。

應該注意的是,要將一個項目附加到一個prolog列表需要遍歷整個列表,由於prolog列表的結構,前置是一個簡單的操作。 我們可以將項目添加到現有列表中,如下所示:

prepend( X , Xs , [X|Xs] ) .

prolog中常見的習語是使用帶有累加器worker謂詞 這使得reverse/2實現更加高效,並且(可能)更容易理解。 在這里,我們通過將累加器設置為空列表來反轉列表。 我們遍歷源列表。 當我們在源列表中遇到項目時,我們將它添加到反向列表中,從而產生反向列表。

reverse(Xs,Ys) :-            % to reverse a list of any length, simply invoke the
  reverse_worker(Xs,[],Ys) . % worker predicate with the accumulator seeded as the empty list

reverse_worker( []     , R , R     ).    % if the list is empty, the accumulator contains the reversed list
reverse_worker( [X|Xs] , T , R     ) :-  % if the list is non-empty, we reverse the list
  reverse_worker( Xs , [X|T] , R )       % by recursing down with the head of the list prepended to the accumulator
  .

現在你有一個在O(n)時間運行的reverse/2實現。 它也是尾遞歸的,這意味着它可以處理任何長度的列表而不會吹掉它的堆棧。

考慮使用DCG,這更容易理解:

reverse([])     --> [].
reverse([L|Ls]) --> reverse(Ls), [L].

例:

?- phrase(reverse([a,b,c]), Ls).
Ls = [c, b, a].

在這種情況下,RevT究竟是什么? 我知道它應該代表T的反向或給定列表的其余部分,但是我沒有看到它如何具有任何值,因為我沒有將它分配給任何東西。 它是否與RevList具有相同的用途,但對於每個遞歸調用?

Prolog中的變量是關系論證的“占位符”。 我們知道,在成功調用之后,正是指定的參數適用於關系。

如果呼叫成功,則RevT將具有值。 具體來說, 列表不為 ,將是調用conc(RevT, [H], RevList)的最后一個參數。 否則,將是空列表。

另外,為什么我必須在我的conc()函數調用中使用[H]而不是H? H不是指列表的頭部(例如:[H])? 或者它只是引用列表頭部的項目(只是H)?

是的,H指的是列表的第一個項目 (通常稱為元素 ),然后我們必須“重塑”它作為一個列表(只有1個元素),如conc / 3所要求的那樣,這是列表中的另一個關系。

只是關於測試 reverse/2謂詞定義的注釋,太長而不適合評論。

反轉列表是引入QuickCheck的“hello world”示例,這意味着您可以使用它來幫助測試您的定義。 首先,我們定義一個為reverse/2謂詞保留的屬性 :反轉列表兩次必須給出原始列表,我們可以將其轉換為:

same_list(List) :-
    reverse(List, Reverse),
    reverse(Reverse, ReverseReverse),
    List == ReverseReverse.

使用Logtalk的lgtunit工具lgtunit實現:

% first argument bound:
| ?- lgtunit::quick_check(same_list(+list)).
% 100 random tests passed
yes

% both arguments unbound
| ?- lgtunit::quick_check(same_list(-list)).
% 100 random tests passed
yes

或者干脆:

% either bound or unbound first argument:
| ?- lgtunit::quick_check(same_list(?list)).
% 100 random tests passed
yes

但我們需要另一個屬性定義來測試第二個參數bound:

same_list_2(Reverse) :-
    reverse(List, Reverse),
    reverse(List, ListReverse),
    Reverse == ListReverse.

我們現在可以這樣做:

% second argument bound:
| ?- lgtunit::quick_check(same_list_2(+list)).
% 100 random tests passed
yes

但請注意,這種基於屬性的/隨機化測試不會檢查非終止情況,因為這些僅在第一個解決方案后回溯時發生。

以下是reverse / 2的典型實現。 然而,它確實存在明顯低於“非終止”的問題。

?- ['/dev/tty'] .

reverse(_source_,_target_) :-
reverse(_source_,_target_,[])  .

reverse([],_target_,_target_)  .

reverse([_car_|_cdr_],_target_,_collect_) :-
reverse(_cdr_,_target_,[_car_|_collect_])  .

end_of_file.

?- reverse([],Q) .
Q = []

?- reverse([a],Q) .
Q = [a]

?- reverse([a,b],Q) .
Q = [b,a]

?- reverse([a,b,c],Q) .
Q = [c,b,a]

?- reverse(P,[]) .
P = [] ? ;
%% non-termination ! %%
^CAction (h for help): a

?- reverse(P,[a]) .
P = [a] ? ;
%% non-termination ! %%
^CAction (h for help): a

?- reverse(P,[a,b]) .
P = [b,a] ? ;
%% non-termination ! %%
^CAction (h for help): a

?- reverse(P,[a,b,c]) .
P = [c,b,a] ? ;
%% non-termination ! %%
^CAction (h for help): a
:- op(2'1,'fy','#') .
:- op(2'1,'fy','##') .
:- op(2'1,'fy','###') .

'反向/ 2'。

/ *以下是我剛剛發明的reverse / 2的實現,它沒有遇到reverse(P,[])的非終止問題。 * /

'實施'。

    reverse(_source_,_target_) :-
    reverse(_source_,_target_,_source_,_target_,[],[]) .

    reverse(_source_,_target_,[],[],_target_,_source_)  .

    reverse(_source_,_target_,[_source_car_|_source_cdr_],[_target_car_|_target_cdr_],_source_collect_,_target_collect_) :-
    reverse(_source_,_target_,_source_cdr_,_target_cdr_,[_source_car_|_source_collect_],[_target_car_|_target_collect_])  .

'測試'。

'測試:第1部分'。

    /*
    ?- reverse([],Q) .
    Q = []

    ?- reverse([a],Q) .
    Q = [a]

    ?- reverse([a,b],Q) .
    Q = [b,a]

    ?- reverse([a,b,c],Q) .
    Q = [c,b,a]

'測試:第2部分'。

    /*
    ?- reverse(P,[]) .
    P = []

    ?- reverse(P,[a]) .
    P = [a]

    ?- reverse(P,[a,b]) .
    P = [b,a]

    ?- reverse(P,[a,b,c]) .
    P = [c,b,a]
    */

'測試:第3部分'。

    /*
    ?- reverse(P,Q) .
    P = Q = [] ? ;
    P = Q = [_A] ? ;
    P = [_A,_B],
    Q = [_B,_A] ? ;
    P = [_A,_B,_C],
    Q = [_C,_B,_A] ? ;
    P = [_A,_B,_C,_D],
    Q = [_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E],
    Q = [_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F],
    Q = [_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G],
    Q = [_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H],
    Q = [_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I],
    Q = [_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J],
    Q = [_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K],
    Q = [_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L],
    Q = [_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M],
    Q = [_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N],
    Q = [_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O],
    Q = [_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P],
    Q = [_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q],
    Q = [_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R],
    Q = [_R,_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S],
    Q = [_S,_R,_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S,_T],
    Q = [_T,_S,_R,_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? ;
    P = [_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S,_T,_U],
    Q = [_U,_T,_S,_R,_Q,_P,_O,_N,_M,_L,_K,_J,_I,_H,_G,_F,_E,_D,_C,_B,_A] ? 
    */

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM