简体   繁体   English

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

[英]Prolog : Recursive Function Branching & “Returning”

I was writing few recursive Prolog predicates and ran into a certain point that I don't quite understand at the moment. 我写了一些递归的Prolog谓词,但遇到了我目前不太了解的某个问题。

For example, I wrote the predicate split/3 that divides a list of integers into a list of non-negative integers and a list of negative integers: 例如,我写了谓词split/3 ,将整数列表分为非负整数列表和负整数列表:

Version 1 版本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).

But before arriving at that solution, I wrote the solution below and wondered for a while why it wasn't working: 但是在获得该解决方案之前,我在下面编写了解决方案,想知道为什么它不起作用:

Version 2 版本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]).

where: 哪里:

  • The first given argument is split into ListHead and ListTail . 给定的第一个参数分为ListHeadListTail
  • If the ListHead element (integer) is greater than or equal to 0, it's prepended to the list of non-negative integers, and used an argument for the recursive call with an unmanipulated Nlist . 如果ListHead元素(整数)大于或等于0,则将其放在非负整数列表的前面,并使用带有参数的Nlist进行递归调用。
  • If the ListHead element (integer) is less than 0, it's prepended to the list of negative integers and used as an argument for the recursive call with an unmanipulated PList . 如果ListHead元素(整数)小于0,则它会放在负整数列表的前面,并用作未操纵的PList的递归调用的参数。

I'm not getting why version 2 doesn't work; 我不明白为什么版本2无法正常工作; it compiles without any warnings, but only ever returns false. 它编译时不会发出任何警告,但只会返回false。 The only difference with the above version is that the prepending of integer elements to Nlist or PList is done within the predicate definition (after the :- operator), and not in the parameters for the predicate call. 与上述版本的唯一区别在于,将整数元素添加到NlistPList是在谓词定义内(在:-运算符之后),而不是在谓词调用的参数中完成。 For me, it makes sense to prepend the result as part of the argument for the next call... 对我来说,将结果作为下一个调用的参数的一部分放在前面是很有意义的...

I feel like I'm missing something about Prolog's way of "searching" recursively calls! 我感觉好像缺少有关Prolog的“搜索”递归调用方式的某些东西!

Could someone explain why version 1 works as intended whereas version 2 does not? 有人可以解释为什么版本1可以按预期工作而版本2不能按预期工作吗?

It does not work because you are loosing the elements when you go back in the backtracking. 它不起作用,因为在回溯中您失去了元素。

In version 1, PList is instantiated with [] when the returns begins, you start to stack elements like this: 在版本1中,当返回开始时,PList用[]实例化,您开始像这样堆叠元素:

[ListHead|PList]    the same as   [ListHead| [] ] at first level.

At the end you have all the list. 最后,您拥有所有列表。

In version 2, Plist remains uninstantiated and cutting condition never satisfy, because you have something like: 在版本2中,Plist仍未实例化,并且切割条件永远无法满足,因为您具有以下内容:

[[]|1,2,3,4,5,6]

and don't match with anything. 而且什么都不配

In version 2 you need to use accumulators (or auxiliary variables) at the end you need to copy the accumulators into real variables like this: 在版本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]).

You call that like this: 您这样称呼:

split([1,2,3,-1,-2,-3] , P, N, [], []);

Let me explain that, the accumulator are initialized and they accumulate your data. 让我解释一下,累加器已初始化,它们会累加您的数据。 The first line only copy the accumulators into the real variables when the list is empty an then the accumulators lost its elements back y recursion (you'll understand if you look at the names of the variables on different backtracking levels) but the real variables remain unchanged through the backtracking. 第一行仅在列表为空时将累加器复制到真实变量中,然后累加器将其元素丢失并递归返回(如果您查看不同回溯级别的变量名称,您将了解到),但是真实变量仍然存在通过回溯保持不变。

You'll need an accumulator for each variable you want to return as a result, or do it like your first version. 对于要作为结果返回的每个变量,您将需要一个累加器,或者像第一个版本一样使用累加器。

You can search for information about accumulators. 您可以搜索有关累加器的信息。

Greets. 问候

Pattern matching it's a key feature of Prolog, accounting for half of the power of the language, and plays together to backtracking, allowing to express control in a elegant, but unusual, way. 模式匹配是Prolog的主要功能,占语言能力的一半,并且可以一起回溯,从而以一种优雅但不寻常的方式来表达控制。 Here is how you could 'correct' the second version 这是您可以“更正”第二个版本的方法

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].

Of course the problem it's that the base case cannot be matched with your original version. 当然,问题在于基本案例无法与您的原始版本匹配。

Re-writing your code with shorter variable names and rule head assignments moved into rule body, it might be easier to read. 用较短的变量名和规则头分配移入规则主体重新编写代码,这可能更易于阅读。

We'll assume it is called with a given list, to produce its two halves, one of its non-negatives (we'll call them "positives", for short, but 0 is included), and another, of its negative elements. 我们假设用给定的列表调用它,以产生它的两半,其中一个是非负数(我们称它们为“正数”,简称,但包括0),而另一个则是它的负数。

Version 1 reads, 第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).
  • an empty list splits into two empty lists; 一个空列表分为两个空列表;
  • to split a list X with head A which is non-negative, we split its tail B into two lists, list of positives POS and list of negatives NEG , and (naturally) prepend A to B 's positives to be returned as X 's positives; 分割一个列表X与头A其是非负的,我们拆分它的尾巴B成两个列表,阳性列表POS和底片的列表NEG ,和(自然)前置AB的肯定作为要返回X '正面
  • to split a list X with head A which is negative, we split its tail B into two lists, list of positives POS and list of negatives NEG , and (naturally) prepend A to B 's negatives, to be returned as X 's negatives. 要将列表X的头A设为负数,我们将其尾部B分为两个列表,即正数POS列表和负数NEG列表,并且(自然地) A置于B的负数之前,以X的形式返回底片。

Version 2 reads, 第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]).
  • an empty list splits into two empty lists; 一个空列表分为两个空列表;
  • to split a list X with head A which is non-negative, we split its tail B into two lists, list of positives and list of negatives, and we demand that the head of B 's positives be the same as A ( which is most unlikely ); 分割一个列表X与头A其是非负的,我们拆分它的尾巴B成两个列表,肯定和否定的列表的列表,我们要求的头部B的阳性是相同的A这是最不可能的 ); and then we return only the tail of B 's positives (ie POS ) as X 's positives ( ie one element shorter...?? ); 然后我们只返回B的正数(即POS )的尾部作为X的正数( 即,短一个元素... ?? );
  • similarly with the negative head element. 与负头元素类似。

I think you can see that this makes no sense. 我认为您可以看到这没有任何意义。

There is no backtracking here , because all the rule's clauses are mutually exclusive (guaranteed to fail on backtracking). 这里没有回溯 ,因为所有规则的子句都是互斥的(保证在回溯时失败)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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