【问题标题】:Prolog : Recursive Function Branching & "Returning"Prolog:递归函数分支和“返回”
【发布时间】:2014-07-27 07:18:40
【问题描述】:

我正在编写一些递归 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 版不能按预期工作吗?

【问题讨论】:

    标签: list recursion prolog


    【解决方案1】:

    它不起作用,因为当您返回回溯时会丢失元素。

    在版本 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, [], []);
    

    让我解释一下,累加器已初始化,它们会累积您的数据。第一行仅在列表为空时将累加器复制到实际变量中,然后累加器在递归过程中丢失其元素(如果您查看不同回溯级别上的变量名称,您会明白),但实际变量仍然存在通过回溯保持不变。

    你需要一个累加器来为每个你想要返回的变量创建一个累加器,或者像你的第一个版本一样。

    您可以搜索有关蓄电池的信息。

    问候。

    【讨论】:

    • 非常感谢您的详细解释,现在说得通了!第一个版本肯定更整洁(也是我应该坚持的那个),但要知道累加器可以用来解决第二个版本的问题。再次感谢!
    【解决方案2】:

    模式匹配它是 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].
    

    当然问题是基本情况无法与您的原始版本匹配。

    【讨论】:

    • 感谢您的帮助 - 由于可能有帮助的其他详细信息,我将上述问题标记为答案,但您也清楚地发现了问题。
    【解决方案3】:

    用更短的变量名和规则头分配重新编写您的代码,并将其移入规则正文,这可能更容易阅读。

    我们假设它是用给定的列表调用的,以产生它的两半,一个是非负数(我们将它们简称为“正数”,简称为“正数”,但包括 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,并且(自然)前置@ 987654335@ 到 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 的正数(即短一个元素...??);
    • 与负头部元素类似。

    我认为您可以看到这没有任何意义。

    这里没有回溯,因为所有规则的子句都是互斥的(保证回溯失败)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 2021-06-18
      • 2011-01-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多