【问题标题】:How to delete the last element from a list in Prolog?如何从 Prolog 的列表中删除最后一个元素?
【发布时间】:2021-06-15 05:28:59
【问题描述】:

我处于以下情况:我有一个列表,我只想从中删除最后一个元素。

我已经实施了以下规则(效果不佳):

deleteLastElement([Only],WithoutLast) :-
    !,
    delete([Only],Only,WithoutLast).
deleteLastElement([_|Tail],WithoutLast) :-
    !,
    deleteLastElement(Tail,WithoutLast).

问题是当我调用它时,列表中的所有元素都被删除了,实际上如果我执行以下语句我得到:

[debug]  ?- deleteLastElement([a,b,c], List).
List = [].

查看痕迹我认为很清楚导致此问题的原因:

[trace]  ?- deleteLastElement([a,b], List).
   Call: (7) deleteLastElement([a, b], _G396) ? creep
   Call: (8) deleteLastElement([b], _G396) ? creep
   Call: (9) lists:delete([b], b, _G396) ? creep
   Exit: (9) lists:delete([b], b, []) ? creep
   Exit: (8) deleteLastElement([b], []) ? creep
   Exit: (7) deleteLastElement([a, b], []) ? creep
List = [].

当达到基本情况时,WithoutLast 列表与 空列表 [] 统一,当执行回溯时,WithoutLast 仍然保持空列表。

这不好。

我正在考虑通过以下操作来实现它:

  1. 在调用删除最后一个元素的谓词之前计算列表中的元素个数。
  2. 递归迭代,每次递减元素个数的值
  3. 如果元素个数是 0 是真的,这意味着这是最后一个元素,所以我从原始列表中删除它

但这在我看来不是很清楚,也不是很好,我想知道是否有针对此问题的声明性好的解决方案。

【问题讨论】:

    标签: list prolog


    【解决方案1】:

    我发现您的分析有点过于复杂。让我们从基本情况开始:

    without_last([_], []).
    

    当您位于最后一个元素时,结果应该是空列表。

    因此,归纳情况必须是我们不在最后一个元素的情况。在我将一些元素附加到任意长列表的情况下,没有最后一个元素的列表只是列表的尾部,没有最后一个元素,当前元素在前面。或者:

    without_last([X|Xs], [X|WithoutLast]) :- 
        without_last(Xs, WithoutLast).
    

    这在各个方面都有效。

    ?- without_last([1,2,3,4], X).
    X = [1, 2, 3] ;
    false.
    
    ?- without_last([1,2], X).
    X = [1] .
    
    ?- without_last([1], X).
    X = [] ;
    false.
    
    ?- without_last([], X).
    false.
    
    ?- without_last(X, [1,2,3]).
    X = [1, 2, 3, _G294].
    
    ?- without_last([1,2,3,4], [1,2,3]).
    true.
    
    ?- without_last([1,2,3,X], [1,2,3]).
    true.
    

    【讨论】:

      【解决方案2】:

      为防止创建无用的选择点,请使用滞后以从第一个参数索引中受益:

      list_butlast([X|Xs], Ys) :-                 % use auxiliary predicate ...
         list_butlast_prev(Xs, Ys, X).            % ... which lags behind by one item
      
      list_butlast_prev([], [], _).
      list_butlast_prev([X1|Xs], [X0|Ys], X0) :-  
         list_butlast_prev(Xs, Ys, X1).           % lag behind by one
      

      示例查询:

      ?- list_butlast([], Xs).
      false.
      
      ?- list_butlast([1], Xs).
      Xs = [].                                    % succeeds deterministically
      
      ?- list_butlast([1,2], Xs).
      Xs = [1].                                   % succeeds deterministically
      
      ?- list_butlast([1,2,3], Xs).
      Xs = [1,2].                                 % succeeds deterministically
      

      另一个方向呢?

      ?- list_butlast(Xs, []).
      Xs = [_A].
      
      ?- list_butlast(Xs, [1,2,3]).
      Xs = [1,2,3,_A].
      

      最一般的查询呢?

      ?- list_butlast(Xs, Ys).
         Xs = [_A]            , Ys = []
      ;  Xs = [_A,_B]         , Ys = [_A]
      ;  Xs = [_A,_B,_C]      , Ys = [_A,_B]
      ;  Xs = [_A,_B,_C,_D]   , Ys = [_A,_B,_C]
      ;  Xs = [_A,_B,_C,_D,_E], Ys = [_A,_B,_C,_D]
      ⋯
      

      【讨论】:

        【解决方案3】:

        如果您开发一个遍历输入列表的每个元素的递归过程,则当您找到将结果列表与空列表统一的最后一个元素时,您的基本情况将停止。然后,从递归调用返回,您只需将所有其他项目添加到结果列表中:

        不使用剪切:

        deleteLastElement([_], []).
        deleteLastElement([Head, Next|Tail], [Head|NTail]):-
          deleteLastElement([Next|Tail], NTail).
        

        当第一个参数列表中只有一个元素时,第一个子句(基本情况)将第二个参数与空列表统一。

        第二个子句指出,当第一个参数是一个包含至少两个元素的列表时,您递归地调用自身(没有头部),并将头部添加到该调用返回的第二个参数。

        其实你不需要在第二个子句中明确表明列表需要至少有两个元素,

        deleteLastElement([Head|Tail], [Head|NTail]):-
          deleteLastElement(Tail, NTail).
        

        当然,您也可以使用append/3 删除列表中的最后一项:

        append(WithoutLast, [_], List).
        

        【讨论】:

          【解决方案4】:

          @repeat 的实现肯定是当前 Prolog 处理器中最高效的实现,但我仍然喜欢为此目的使用 DCG - 暗中希望有一天实现技术足以以相当的(空间)效率运行它。

          list_butlast(Xs, Ys) :-
             phrase( ( seq(Ys), [_] ), Xs).
          
          seq([]) -->
             [].
          seq([E|Es]) -->
             [E],
             seq(Es).
          

          【讨论】:

            【解决方案5】:

            从列表中删除最后一个元素的最简单方法是:

            • 反转列表
            • 现在删除第一个元素
            • 反转剩余列表

            你应该使用的代码:

            delete_last(X,Y):-
                reverse(X,[_|X1]), reverse(X1,Y).
            

            【讨论】:

              【解决方案6】:

              这是一个老问题,但我发现了另外两种方法:

              • 不要创建无用的选择点。
              • 不需要额外的谓词。
              findall(X ,append(X, [_], List), [X]).
              
              length(List, N), nth1(N, List, _, X).
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-08-09
                • 1970-01-01
                • 2018-03-10
                • 1970-01-01
                相关资源
                最近更新 更多