7

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

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

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,则表示这是最后一个元素,因此我将其从原始列表中删除

但这在我看来并不清楚而且不太好,我会知道是否有针对此问题的声明性良好解决方案。

4

6 回答 6

13

我觉得你的分析有点过于复杂。让我们从基本情况开始:

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.
于 2013-04-23T16:59:35.607 回答
10

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

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]
⋯
于 2015-08-15T17:24:44.310 回答
9

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

不使用剪切:

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

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

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

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

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

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

append(WithoutLast, [_], List).
于 2013-04-23T16:55:18.460 回答
5

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

list_butlast(Xs, Ys) :-
   phrase( ( seq(Ys), [_] ), Xs).

seq([]) -->
   [].
seq([E|Es]) -->
   [E],
   seq(Es).
于 2016-05-05T07:55:54.547 回答
1

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

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

您应该使用的代码:

delete_last(X,Y):-
    reverse(X,[_|X1]), reverse(X1,Y).
于 2021-05-26T21:42:41.693 回答
1

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

  • 不要创造一个无用的选择点。
  • 不需要额外的谓词。
findall(X ,append(X, [_], List), [X]).
length(List, N), nth1(N, List, _, X).
于 2021-06-10T09:26:05.760 回答