我写了几个递归的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不起作用;它编译时没有任何警告,但只返回错误。与上述版本的唯一区别在于,整数元素前置Nlist
或PList
是在谓词定义(:-
运算符之后)内完成的,而不是在{{1}}运算符之后谓词调用。对我来说,将结果作为下一次调用的参数的一部分进行前置是有意义的......
我觉得我错过了Prolog以“搜索”递归电话的方式!
有人可以解释为什么版本1按预期工作而版本2没有?
答案 0 :(得分:0)
它不起作用,因为当你回到后退时,你正在失去元素。
在版本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, [], []);
让我解释一下,累加器已初始化并累积数据。当列表为空时,第一行只将累加器复制到实变量中然后累加器将其元素丢失回y递归(如果你查看不同回溯级别上变量的名称,你将会理解)但真实的变量通过回溯保持不变。
你需要为你想要返回的每个变量都有一个累加器,或者像你的第一个版本一样。
您可以搜索有关累加器的信息。
电贺。
答案 1 :(得分:0)
模式匹配它是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].
当然问题是基本情况无法与原始版本匹配。
答案 2 :(得分:0)
使用较短的变量名重写代码并将规则头分配移入规则体,可能更容易阅读。
我们假设它被一个给定的列表调用,产生它的两个半部分,其中一个非负数(我们称之为“正数”,简称为0,但是包括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
和否定列表{{ 1}},并且(自然地)将NEG
添加到A
的否定内容,作为B
的否定值返回。版本2 读取,
X
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
的正面(即一个元素更短...... ?? ); 我认为你可以看出这没有任何意义。
此处没有回溯,因为所有规则的子句都是互斥的(保证在回溯时失败)。