【问题标题】:Reversing a List in Prolog在 Prolog 中反转列表
【发布时间】:2013-10-28 14:27:56
【问题描述】:

我已经完成了我的编程课的家庭作业。我应该创建一个反转列表的 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)?

请帮我解决这个问题。我很难理解这种编程背后的逻辑。

【问题讨论】:

标签: list prolog concatenation reverse head


【解决方案1】:

您的解决方案说明: 如果我们反转空列表,我们将获得空列表。 如果我们反转列表 [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=lpn-htmlse25

【讨论】:

  • 感谢您的回答,这对我很有帮助。我有一个问题:Z 变量是做什么用的?
  • @TomOakley Z 是最终结果。
【解决方案2】:

考虑改用 DCG,这样更容易理解:

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

例子:

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

【讨论】:

  • 什么是 DCG?我不明白那个缩写。
  • 更多信息请参见the DCG tag description,相关问题和答案请参见dcg标签!
  • "考虑用 DCG 代替,这样更容易理解" --- DCG 很难理解,假装没有用。
  • reverse([a,b,c],Q] 一起工作很好,但是!不终止 reverse(P,[a,b,c])
【解决方案3】:

在这种情况下,RevT 到底是什么?我知道它应该代表 T 的反向或给定列表的其余部分,但我看不出它如何具有任何价值,因为我没有将它分配给任何东西。它是否与 RevList 的目的相同,但用于每个递归调用?

Prolog 中的变量是关系参数的“占位符”。我们知道,在成功调用之后,正是指定的参数对 that 关系成立。

如果调用成功,RevT 将有一个值。具体来说,将是调用conc(RevT, [H], RevList) 的最后一个参数,列表为空。否则,将是空列表。

另外,为什么我必须在 conc() 函数调用中使用 [H] 而不是仅使用 H? H 不是指列表的头部(例如:[H])吗?或者它只是指列表头部的项目(只是 H)?

是的,H 指的是列表的第一个 item(通常称为 element),那么我们必须将它“重塑”为一个列表(只有 1 个元素) ),根据 conc/3 的要求,这是 lists 之间的另一种关系。

【讨论】:

    【解决方案4】:

    Prolog 列表是简单的数据结构:./2

    • 空列表是原子[]
    • 一个元素的列表,[a],实际上是这样的结构:.(a,[])
    • 两个元素的列表,[a,b]其实就是这个结构:.(a,.(b,[]))
    • 三个元素的列表,[a,b,c]其实就是这个结构:.(a,.(b,.(c,[])))
    • 等等。

    方括号表示法是语法糖,可以让您免于花费一生的时间输入括号。更不用说它对眼睛更容易。

    由此,我们得到列表的head(最外层./2结构中的数据)和列表的tail(子列表)的概念包含在最外层的./2 数据结构中。

    这本质上与您在 C 中看到的经典单链表的数据结构相同:

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

    next 指针为 NULL 或其他列表结构。

    因此,我们得到了 reverse/2 的简单 [naive] 实现:

    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(n2) 的行为。它也不是尾递归的,这意味着足够长的列表会导致堆栈溢出。

    应该注意,将一个项目附加到序言列表需要遍历整个列表,由于序言列表的结构,前置是一个微不足道的操作。我们可以将一个项目添加到现有列表中,如下所示:

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

    prolog 中的一个常见习惯用法是使用 worker 谓词accumulator。这使得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 实现。它也是尾递归的,这意味着它可以处理任意长度的列表而不会破坏其堆栈。

    【讨论】:

      【解决方案5】:

      以下是 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
      

      【讨论】:

        【解决方案6】:
        :- op(2'1,'fy','#') .
        :- op(2'1,'fy','##') .
        :- op(2'1,'fy','###') .
        

        'reverse/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] ? 
            */
        

        【讨论】:

        • 哇!太不可思议了。
        • @MostowskiCollapse 是的,这太不可思议了;这看似简单的事情背后,隐藏着大量的努力和挫折。
        【解决方案7】:

        只是关于测试 reverse/2 谓词定义的注释,太长了,不适合评论。

        反转列表是引入 QuickCheck 的“hello world”示例,这意味着您可以使用它来帮助测试您的定义。首先,我们为reverse/2 谓词定义一个属性:反转一个列表两次必须给出原始列表,我们可以将其转换为:

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

        使用 Logtalk 的 lgtunit 工具 QuickCheck 实现:

        % 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
        

        但我们需要另一个属性定义来测试绑定的第二个参数:

        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
        

        但请注意,这种基于属性的/随机测试不会检查非终止情况,因为这些情况仅在第一个解决方案后回溯时发生。

        【讨论】:

          猜你喜欢
          • 2014-04-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多