繁体   English   中英

Prolog:递归函数分支和“返回”

[英]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]).

哪里:

  • 给定的第一个参数分为ListHeadListTail
  • 如果ListHead元素(整数)大于或等于0,则将其放在非负整数列表的前面,并使用带有参数的Nlist进行递归调用。
  • 如果ListHead元素(整数)小于0,则它会放在负整数列表的前面,并用作未操纵的PList的递归调用的参数。

我不明白为什么版本2无法正常工作; 它编译时不会发出任何警告,但只会返回false。 与上述版本的唯一区别在于,将整数元素添加到NlistPList是在谓词定义内(在:-运算符之后),而不是在谓词调用的参数中完成。 对我来说,将结果作为下一个调用的参数的一部分放在前面是很有意义的...

我感觉好像缺少有关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 ,和(自然)前置AB的肯定作为要返回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.

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