我在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).
我可以看到为什么列表会通过查看我的代码而反转,但我无法找到解决方法。如果我尝试反转将非素数移动到中间列表的递归情况的头部和尾部,它会以正确的顺序运行并出现,但是每个值都在自己的嵌套列表中,这比出来更糟糕向后。
有没有一种简单的方法可以解决这个问题?
答案 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}}而不是。
新代码演示了“差异列表”技术:逻辑变量Z
和A
形成一对,描述从X
到X
的列表前缀A
作为列表X
与其尾A
之间的“差异”。
因此,我们在接口调用中明确地获取结束值:
primeremover(L, Lcomp):-
primeremover(L, E, Lcomp), E = [].
我们可以根据需要使用E
的任何值,而不仅仅是硬编码的[]
,直接调用primeremover/3
谓词。
这在Prolog中编码实际上比通常的命令式“累加器”技术(通过 cons ,prepending)更自然,除非我们实际上需要以相反的顺序构建我们的结果。尽管如此,将元素附加到开放式列表的末尾可以理所当然地被视为累积。