Prolog列表意外逆转

时间:2014-03-17 15:40:31

标签: list prolog primes

我在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).

我可以看到为什么列表会通过查看我的代码而反转,但我无法找到解决方法。如果我尝试反转将非素数移动到中间列表的递归情况的头部和尾部,它会以正确的顺序运行并出现,但是每个值都在自己的嵌套列表中,这比出来更糟糕向后。

有没有一种简单的方法可以解决这个问题?

4 个答案:

答案 0 :(得分:4)

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

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)。

答案 1 :(得分:2)

您不需要调用辅助函数来执行此操作,您可以仅使用输入变量和返回来直接执行此操作

代码:

primeremover([], []).

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

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

答案 2 :(得分:2)

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

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

请注意,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)
    ).

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

答案 3 :(得分:1)

以下是代码的最小编辑,以解决问题。

%%// 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).

而不是 prepending - 在前面添加元素到累加器,并从最深的调用返回它的最终值,我们追加 - 在末尾添加 - 元素返回列表,并在最深的调用中将其最终的结束指针设置为[]。两者基本上都是迭代进程,并且两者都由Prolog编译,即使用常量控制堆栈空间。

两个变体都是尾递归,但新变量是尾递归modulo cons

因为变量A不再用作累加器,而是用作最终的“指针”(列表的最后 cons 单元格),所以通常称它为{{ 1}}而不是。

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

因此,我们在接口调用中明确地获取结束值:

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

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

这在Prolog中编码实际上比通常的命令式“累加器”技术(通过 cons ,prepending)更自然,除非我们实际上需要以相反的顺序构建我们的结果。尽管如此,将元素附加到开放式列表的末尾可以理所当然地被视为累积。