[英]Prolog : Recursive Function Branching & “Returning”
我写了一些递归的Prolog谓词,但遇到了我目前不太了解的某个问题。
例如,我写了谓词split/3
,将整数列表分为非负整数列表和负整数列表:
版本1
split([], [], []).
split([ListHead|ListTail], [ListHead|PList], NList) :-
ListHead >= 0,
split(ListTail, PList, NList).
split([ListHead|ListTail], PList, [ListHead|NList]) :-
ListHead < 0,
split(ListTail, PList, NList).
但是在获得该解决方案之前,我在下面编写了解决方案,想知道为什么它不起作用:
版本2
split([], [], []).
split([ListHead|ListTail], PList, NList) :-
ListHead >= 0,
split(ListTail, [ListHead|PList], NList).
split([ListHead|ListTail], PList, NList) :-
ListHead < 0,
split(ListTail, PList, [ListHead|NList]).
哪里:
ListHead
和ListTail
。 ListHead
元素(整数)大于或等于0,则将其放在非负整数列表的前面,并使用带有参数的Nlist
进行递归调用。 ListHead
元素(整数)小于0,则它会放在负整数列表的前面,并用作未操纵的PList
的递归调用的参数。 我不明白为什么版本2无法正常工作; 它编译时不会发出任何警告,但只会返回false。 与上述版本的唯一区别在于,将整数元素添加到Nlist
或PList
是在谓词定义内(在:-
运算符之后),而不是在谓词调用的参数中完成。 对我来说,将结果作为下一个调用的参数的一部分放在前面是很有意义的...
我感觉好像缺少有关Prolog的“搜索”递归调用方式的某些东西!
有人可以解释为什么版本1可以按预期工作而版本2不能按预期工作吗?
它不起作用,因为在回溯中您失去了元素。
在版本1中,当返回开始时,PList用[]实例化,您开始像这样堆叠元素:
[ListHead|PList] the same as [ListHead| [] ] at first level.
最后,您拥有所有列表。
在版本2中,Plist仍未实例化,并且切割条件永远无法满足,因为您具有以下内容:
[[]|1,2,3,4,5,6]
而且什么都不配
在版本2中,最后需要使用累加器(或辅助变量),您需要将累加器复制到真实变量中,如下所示:
split([],A,B,A,B).
split([ListHead|ListTail], PList, NList, PListAcum, NListAcum) :- ListHead >= 0,
split(ListTail, PList, NList, [ListHead|PListAcum], NListAcum).
split([ListHead|ListTail], PList, NList, PListAcum, NListAcum) :- ListHead < 0,
split(ListTail, PList, NList, PListAcum, [ListHead|NListAcum]).
您这样称呼:
split([1,2,3,-1,-2,-3] , P, N, [], []);
让我解释一下,累加器已初始化,它们会累加您的数据。 第一行仅在列表为空时将累加器复制到真实变量中,然后累加器将其元素丢失并递归返回(如果您查看不同回溯级别的变量名称,您将了解到),但是真实变量仍然存在通过回溯保持不变。
对于要作为结果返回的每个变量,您将需要一个累加器,或者像第一个版本一样使用累加器。
您可以搜索有关累加器的信息。
问候
模式匹配是Prolog的主要功能,占语言能力的一半,并且可以一起回溯,从而以一种优雅但不寻常的方式来表达控制。 这是您可以“更正”第二个版本的方法
split([],[],[]).
split([ListHead|ListTail], PList, NList) :-
ListHead >= 0, split(ListTail, Temp, NList), PList=[ListHead|Temp].
split([ListHead|ListTail], PList, NList) :-
ListHead < 0, split(ListTail, PList, Temp), NList=[ListHead|Temp].
当然,问题在于基本案例无法与您的原始版本匹配。
用较短的变量名和规则头分配移入规则主体重新编写代码,这可能更易于阅读。
我们假设用给定的列表调用它,以产生它的两半,其中一个是非负数(我们称它们为“正数”,简称,但包括0),而另一个则是它的负数。
第1版内容如下:
split([],[],[]).
split(X, Y, Z) :- X = [A|B], Y = [A|POS], Z = NEG,
A >= 0, split(B, POS, NEG).
split(X, Y, Z) :- X = [A|B], Y = POS, Z = [A|NEG],
A < 0, split(B, POS, NEG).
X
与头A
其是非负的,我们拆分它的尾巴B
成两个列表,阳性列表POS
和底片的列表NEG
,和(自然)前置A
到B
的肯定作为要返回X
'正面 X
的头A
设为负数,我们将其尾部B
分为两个列表,即正数POS
列表和负数NEG
列表,并且(自然地) A
置于B
的负数之前,以X
的形式返回底片。 第2版内容如下:
split([],[],[]).
split(X, Y, Z) :- X = [A|B], Y = POS, Z = NEG,
A >= 0, split(B, [A|POS], NEG).
split(X, Y, Z) :- X = [A|B], Y = POS, Z = NEG,
A < 0, split(B, POS, [A|NEG]).
X
与头A
其是非负的,我们拆分它的尾巴B
成两个列表,肯定和否定的列表的列表,我们要求的头部B
的阳性是相同的A
( 这是最不可能的 ); 然后我们只返回B
的正数(即POS
)的尾部作为X
的正数( 即,短一个元素... ?? ); 我认为您可以看到这没有任何意义。
这里没有回溯 ,因为所有规则的子句都是互斥的(保证在回溯时失败)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.