如何创建一个删除倒数第二个元素的Prolog谓词?

时间:2013-10-29 21:28:36

标签: recursion prolog predicate

我需要帮助创建一个谓词,删除列表的倒数第二个元素并返回用Prolog编写的列表。到目前为止我已经

remove([],[]).
remove([X],[X]).
remove([X,Y],[Y]).

就我而言。我需要找出一种递归遍历列表的方法,直到它只有两个元素长,然后重新组合要返回的列表。如果可以,请帮助解释。

2 个答案:

答案 0 :(得分:3)

到目前为止,您的定义是完美的!它有点过于专业化,所以我们必须扩展它。但是你的计划是一个坚实的基础。

你“只”需要扩展它。

remove([],[]).
remove([X],[X]).
remove([_,X],[X]).
remove([X,_,Y], [X,Y]).
remove([X,Y,_,Z], [X,Y,Z]).
remove([X,Y,Z,_,Z2], [X,Y,Z,Z2]).
...

好的,你知道如何继续。现在,让我们确定常见案例:

...
remove([X,Y,_,Z], [X,Y,Z]).
%       ^^^        ^^^  
remove([X,Y,Z,_,Z2], [X,Y,Z,Z2]).
%       ^^^^^         ^^^^^
...

所以,我们有一个共同的列表前缀。我们可以说:

  

每当我们有一个列表及其删除列表时,我们可以得出结论,通过在两边添加一个元素,我们得到一个更长的列表。

remove([X|Xs], [X|Ys]) :-
   remove(Xs,Ys).

请注意,:-实际上是一个箭头。这意味着:如果在右侧提供的是真实的,那么左侧的内容也是如此。

H-h-等一下!这是真的吗?如何测试? (如果你只测试阳性病例,你总会得到“是”。)我们没有时间想出一些测试用例,对吗?让我们让Prolog为我们付出艰辛的努力吧!所以,Prolog,填补空白!

remove([],[]).
remove([X],[X]).
remove([_,X],[X]).
remove([X|Xs], [X|Ys]) :-
   remove(Xs,Ys).

| ?- remove(Xs,Ys). % most general goal

Xs = [], Ys = [] ;

Xs = [A], Ys = [A] ;

Xs = [_,A], Ys = [A] ;

Xs = [A], Ys = [A] ;         % redundant, but OK

Xs = [A,B], Ys = [A,B] ;     % WRONG

Xs = [A,_,B], Ys = [A,B] ;

Xs = [A,B], Ys = [A,B] ;     % WRONG again!

Xs = [A,B,C], Ys = [A,B,C] ; % WRONG

Xs = [A,B,_,C], Ys = [A,B,C] ...

拒绝一切并从头开始重新开始是很诱人的。 但是在Prolog你可以做得更好,所以让我们冷静下来估计实际的伤害: 有些答案不正确。一些答案是正确的。

可能我们目前的定义有点过于笼统。

为了更好地了解情况,我将详细研究意外的成功remove([1,2],[1,2])。谁是罪魁祸首?

即使是以下程序切片/片段也会成功。

remove([],[]).
remove([X],[X]) :- false.
remove([_,X],[X]) :- false.
remove([X|Xs], [X|Ys]) :-
   remove(Xs,Ys).

虽然这是我们程序的特化,但它会读取:对于所有相同的列表,remove / 2成立。那不可能是真的!要解决这个问题,我们必须在剩下的可见部分做一些事情。我们必须专注于它。这里有问题的是递归规则也适用于:

remove([1,2], [1,2]) :-
   remove([2], [2]).
remove([2], [2]) :-
   remove([], []).

必须避免这种结论。我们需要通过添加另一个目标(=)/2将规则限制为列表中至少还有两个元素。

remove([X|Xs], [Y|Ys]) :-
   Xs = [_,_|_],
   remove(Xs, Ys).

那么我们的错误是什么?在非正式的

  

每当我们有一个列表及其删除列表时,......

术语“删除列表”含糊不清。这可能意味着我们在这里指的是关系remove / 2(这是不正确的,因为remove([],[])成立,但仍然没有删除),或者我们在这里指的是一个删除了元素的列表。这些错误不可避免地发生在编程中,因为你想通过使用比Prolog本身更不正式的语言来重新保持你的直觉。

作为参考,这里再次(和与其他定义进行比较)是最终定义:

remove([],[]).
remove([X],[X]).
remove([_,X],[X]).
remove([X|Xs], [X|Ys]) :-
   Xs = [_,_|_],
   remove(Xs,Ys).

有更有效的方法可以做到这一点,但这是最直接的方式。

答案 1 :(得分:1)

如果您只考虑“倒数第二个元素”的含义,我将尝试提供另一个更容易构建的解决方案,并明确描述每个可能的情况:

rem_2nd_last([], []).
rem_2nd_last([First|Rest], R) :-
    rem_2nd_last_2(Rest, First, R). % "Lag" the list once

rem_2nd_last_2([], First, [First]).
rem_2nd_last_2([Second|Rest], First, R) :-
    rem_2nd_last_3(Rest, Second, First, R). % "Lag" the list twice

rem_2nd_last_3([], Last, _SecondLast, [Last]). % End of list: drop second last
rem_2nd_last_3([This|Rest], Prev, PrevPrev, [PrevPrev|R]) :-
    rem_2nd_last_3(Rest, This, Prev, R). % Rest of list

在三个谓词的定义中,解释隐藏在普通视图中。

“滞后”是一种从列表末尾返回的方法,但保持谓词始终具有确定性。您只需抓取一个元素并将列表的其余部分作为辅助谓词的第一个参数传递。例如,定义last/2的一种方法是:

last([H|T], Last) :-
    last_1(T, H, Last).

last_1([], Last, Last).
last_1([H|T], _, Last) :-
    last_1(T, H, Last).