简体   繁体   English

序言清单意外倒转

[英]Prolog list accidentally reversing

I am having trouble writing code in Prolog which takes a list, removes all primes, and returns the rest of the list. 我在Prolog中编写代码时遇到麻烦,该代码需要一个列表,删除所有素数,然后返回列表的其余部分。 I have written a prime checker predicate, prime/1 , which works just fine, but when I apply my program to the list, as with almost anything I try to do in Prolog, I get back the list without the primes, as wanted, but in reverse order. 我已经写了一个素数检查谓词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).

I can see why the list comes back reversed by looking at my code, but I just can't find a way around it. 通过查看我的代码,我可以看到为什么列表又回来了,但是我找不到解决方法。 If I try reversing the head and tail of the recursive case which moves the non-prime into the middle list, it works and comes out in the correct order, but with every value in its own nested list, which is even worse than coming out backwards. 如果我尝试反转递归案例的首尾,将非素数移到中间列表中,它将正常工作并以正确的顺序出现,但是每个值都在其自己的嵌套列表中,这比出现时更糟向后。

Is there a simple way to correct this issue? 有没有简单的方法可以解决此问题?

I think this fixes your problem: 我认为这可以解决您的问题:

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

I'm not sure if I am missing something, but I believe that you are approaching this as a functional language rather than a logical language. 我不确定是否遗漏了一些东西,但是我相信您正在以一种功能性语言而不是逻辑性语言来对待它。 Also, the third argument doesn't really seem to add something to the solution; 同样,第三个参数似乎并没有真正为解决方案添加任何东西; it can be removed without loss of functionality. 可以删除它而不会失去功能。

I don't have your prime predicate but I used this to test: 我没有您的prime谓词,但是我用它来测试:

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

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

I used GNU Prolog (1.4.0). 我使用了GNU Prolog(1.4.0)。

You don't need to call an auxiliar function to do this, you can do it directly by using only the input variable and the return 您无需调用辅助函数即可执行此操作,只需使用输入变量和return即可直接执行此操作

Code: 码:

primeremover([], []).

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

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

You're using in your primeremover/3 predicate the second argument as an accumulator , ie an auxiliary argument that works as a stack, collecting intermediate results. 您在primeremover/3使用第二个参数作为累加器 ,即作为堆栈的辅助参数,收集中间结果。 This (useful) technique is often used in the definition of recursive predicates to get the benefits of tail recursion . 经常在递归谓词的定义中使用这种(有用的)技术来获得尾递归的好处。 A canonical example of this technique is the definition of a predicate for reversing a list. 该技术的一个典型示例是用于反转列表的谓词的定义。 The naive definition is not tail recursive and thus requires space proportional to the length of the list: 天真的定义不是尾递归的,因此需要与列表长度成比例的空间:

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

Note that the recursive call in the second clause to the reverse/2 predicate is not the last call. 请注意,对verse reverse/2谓词的第二个子句中的递归调用不是最后一个调用。 Thus, the append/3 predicate call that follows must be suspended by saving it in a stack until the recursive reverse/2 predicate terminates. 因此,必须通过将其后面的append/3谓词调用保存到堆栈中来挂起,直到递归的reverse/2谓词终止。 This stack grows one element per each recursive call. 该堆栈在每个递归调用中增长一个元素。 But this stack will not be required if the recursive call is the last call. 但是,如果递归调用是最后一个调用,则不需要此堆栈。 The tail recursive definition can be coded using an accumulator: 尾递归定义可以使用累加器进行编码:

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

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

But, in your specific case, and as both Erwin and Guillermo explained, there's no need to use an accumulator as you can construct the output list as you traverse the input list. 但是,在您的特定情况下,正如Erwin和Guillermo都解释的那样,由于在遍历输入列表时可以构造输出列表,因此无需使用累加器。 The code they suggested can be, however, arguably improved by avoiding testing if the current head of the input list is a prime twice (in the case of Erwin solution) and also by avoiding a cut (in case of the Guillermo solution) by using Prolog's standard if-then-else control construct: 但是,可以建议通过避免使用输入列表的当前头是否是素数两次的测试(对于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)
    ).

Note that this version is (also) tail recursive. 请注意,此版本(也是)尾部递归。

Here is a minimal edit of your code to correct the issue. 这是对代码进行的最小编辑,以解决此问题。

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

Instead of prepending - adding at the front - elements to an accumulator, and returning its final value from the deepest invocation, we append - add at the end - elements to the return list, and set its final end pointer as [] at the deepest invocation. 添加在前面- -代替前面附加的元素添加到蓄能器和从最深调用返回它的最终值,我们附加 -添加在末端-元素在返回表,并设置其最后端的指针作为[]在最深调用。 Both are essentially iterative processes, and both are compiled by Prolog as such, ie with constant control stack space use. 两者本质上都是迭代过程,并且都由Prolog本身进行编译,即使用恒定的控制堆栈空间。

Both variants are tail recursive, but the new one is tail recursive modulo cons . 两种变体都是尾部递归的,但是新的变体是尾部递归的模态缺点

Because the variable A no longer serves as an accumulator, and is used instead as a final "pointer" (list's last cons cell), it is customary to call it Z instead. 由于变量A不再用作累加器,而是用作最终的“指针”(列表的最后一个cons单元格),因此习惯上将其称为Z

The new code demonstrates the "difference list" technique: the logical variables A and X form a pair, describing a list's prefix from X to A as a "difference" between a list X and its tail A . 新代码演示了“差异列表”技术:逻辑变量AX组成一对,将列表从XA的前缀描述为列表X与其尾部A之间的“差异”。

We thus get hold of the end value explicitly, in the interface call: 因此,我们在接口调用中显式地获得了终止值:

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

We could use any value for E , as we need, not just the hard-coded [] , by directly calling the primeremover/3 predicate. 通过直接调用primeremover/3谓词,我们可以根据需要为E使用任何值,而不仅仅是硬编码的[]

This is actually more natural to code in Prolog, than the usual imperative "accumulator" technique (by cons , prepending), unless we actually need to build our result in reversed order. 与通常的命令式“累加器”技术(使用cons ,前置)相比,用Prolog编写代码实际上更自然,除非我们实际上需要以相反的顺序构建结果。 Although, appending elements to the end of open-ended list can just as rightfully be seen as accumulating. 虽然,将元素追加到不限成员名额列表的末尾也可以视为累积。

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

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