[英]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
新代码演示了“差异列表”技术:逻辑变量A
和X
组成一对,将列表从X
到A
的前缀描述为列表X
与其尾部A
之间的“差异”。
因此,我们在接口调用中显式地获得了终止值:
primeremover(L, Lcomp):-
primeremover(L, E, Lcomp), E = [].
通过直接调用primeremover/3
谓词,我们可以根据需要为E
使用任何值,而不仅仅是硬编码的[]
。
与通常的命令式“累加器”技术(使用cons ,前置)相比,用Prolog编写代码实际上更自然,除非我们实际上需要以相反的顺序构建结果。 虽然,将元素追加到不限成员名额列表的末尾也可以视为累积。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.