繁体   English   中英

序言清单意外倒转

[英]Prolog list accidentally reversing

我在Prolog中编写代码时遇到麻烦,该代码需要一个列表,删除所有素数,然后返回列表的其余部分。 我已经写了一个素数检查谓词prime/1 ,它可以很好地工作,但是当我将程序应用到列表中时,就像我在Prolog中尝试执行的几乎所有操作一样,我会按需返回没有素数的列表,但顺序相反。

primemover(L, Lcomp):-
    primeremover(L, [], Lcomp).
primeremover([], A, A).
primeremover([H | T], A, X):-
    \+ prime(H),
    primeremover(T, [H | A], X).
primeremover([H | T], A, X):-
    prime(H),
    primeremover(T, A, X).

通过查看我的代码,我可以看到为什么列表又回来了,但是我找不到解决方法。 如果我尝试反转递归案例的首尾,将非素数移到中间列表中,它将正常工作并以正确的顺序出现,但是每个值都在其自己的嵌套列表中,这比出现时更糟向后。

有没有简单的方法可以解决此问题?

我认为这可以解决您的问题:

primeremover([], []).
primeremover([H | T], [H | Y]):-
    \+ prime(H),
    primeremover(T, Y).
primeremover([H | T], Y):-
    prime(H),
    primeremover(T, Y).

我不确定是否遗漏了一些东西,但是我相信您正在以一种功能性语言而不是逻辑性语言来对待它。 同样,第三个参数似乎并没有真正为解决方案添加任何东西; 可以删除它而不会失去功能。

我没有您的prime谓词,但是我用它来测试:

main :- primeremover([1,2,3,4,5], A), write(A).

prime(X) :- X = 2; X = 3; X = 5.

我使用了GNU Prolog(1.4.0)。

您无需调用辅助函数即可执行此操作,只需使用输入变量和return即可直接执行此操作

码:

primeremover([], []).

primeremover([H | T], Y):-
    prime(H),
    !,
    primeremover(T, Y).

primeremover([H | T], [H | Y]):-
        primeremover(T, Y).

您在primeremover/3使用第二个参数作为累加器 ,即作为堆栈的辅助参数,收集中间结果。 经常在递归谓词的定义中使用这种(有用的)技术来获得尾递归的好处。 该技术的一个典型示例是用于反转列表的谓词的定义。 天真的定义不是尾递归的,因此需要与列表长度成比例的空间:

reverse([], []).
reverse([Head| Tail], Reversed) :-
    reverse(Tail, Reversed0),
    append(Reversed0, [Head], Reversed).

请注意,对verse reverse/2谓词的第二个子句中的递归调用不是最后一个调用。 因此,必须通过将其后面的append/3谓词调用保存到堆栈中来挂起,直到递归的reverse/2谓词终止。 该堆栈在每个递归调用中增长一个元素。 但是,如果递归调用是最后一个调用,则不需要此堆栈。 尾递归定义可以使用累加器进行编码:

reverse(List, Reversed) :-
    reverse(List, [], Reversed).

reverse([], Reversed, Reversed).
reverse([Head| Tail], List, Reversed) :-
    reverse(Tail, [Head| List], Reversed).

但是,在您的特定情况下,正如Erwin和Guillermo都解释的那样,由于在遍历输入列表时可以构造输出列表,因此无需使用累加器。 但是,可以建议通过避免使用输入列表的当前头是否是素数两次的测试(对于Erwin解决方案),以及避免使用割线(对于Guillermo解决方案),可以改进他们建议的代码。 Prolog的标准if-then-else控制构造:

prime_remover([], []).
prime_remover([Head| Tail], NonPrimes):-
    (   prime(Head) ->
        prime_remover(Tail, NonPrimes)
    ;   NonPrimes = [Head| NonPrimesTail),
        prime_remover(Tail, NonPrimesTail)
    ).

请注意,此版本(也是)尾部递归。

这是对代码进行的最小编辑,以解决此问题。

%%// primeremover( +L, -Lcomp) 
primeremover(L, Lcomp):-             %// was:
    primeremover(L, [], Lcomp).
primeremover([], A, A).
primeremover([H | T], A, [H | X]):-  %// primeremover([H | T], A, X):-
    \+ prime(H),
    primeremover(T, A, X).           %//     primeremover(T, [H | A], X).
primeremover([H | T], A, X):-
    prime(H),
    primeremover(T, A, X).

添加在前面- -代替前面附加的元素添加到蓄能器和从最深调用返回它的最终值,我们附加 -添加在末端-元素在返回表,并设置其最后端的指针作为[]在最深调用。 两者本质上都是迭代过程,并且都由Prolog本身进行编译,即使用恒定的控制堆栈空间。

两种变体都是尾部递归的,但是新的变体是尾部递归的模态缺点

由于变量A不再用作累加器,而是用作最终的“指针”(列表的最后一个cons单元格),因此习惯上将其称为Z

新代码演示了“差异列表”技术:逻辑变量AX组成一对,将列表从XA的前缀描述为列表X与其尾部A之间的“差异”。

因此,我们在接口调用中显式地获得了终止值:

primeremover(L, Lcomp):-        
    primeremover(L, E, Lcomp), E = [].

通过直接调用primeremover/3谓词,我们可以根据需要为E使用任何值,而不仅仅是硬编码的[]

与通常的命令式“累加器”技术(使用cons ,前置)相比,用Prolog编写代码实际上更自然,除非我们实际上需要以相反的顺序构建结果。 虽然,将元素追加到不限成员名额列表的末尾也可以视为累积。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM