List Prolog语言中的Size过程

List Prolog语言中的Size过程,list,recursion,prolog,List,Recursion,Prolog,我对Prolog不熟悉,在递归算法方面也不是很好,因此我对以下两个条款感到困惑: size([], 0). size([H|T], N) :- size(T, N1), N is N1+1. 我无法追踪此问题的原因: ?- size([a,b,c,d], N). 这将与第二条统一,形成: size([a,b,c,d], N) :- size([b,c,d], N1), N is N1+1. 但我对NisN1+1感到困惑,因为这些变量从未统一过。这些变量取什么值 任何关于这个问题的帮助,或者

我对Prolog不熟悉,在递归算法方面也不是很好,因此我对以下两个条款感到困惑:

size([], 0).
size([H|T], N) :- size(T, N1), N is N1+1.
我无法追踪此问题的原因:

?- size([a,b,c,d], N).
这将与第二条统一,形成:

size([a,b,c,d], N) :- size([b,c,d], N1), N is N1+1.
但我对
N
is
N1+1
感到困惑,因为这些变量从未统一过。这些变量取什么值

任何关于这个问题的帮助,或者这个算法的简单跟踪,都将不胜感激

N为N1+1,因为这些变量从未统一

我想你的意思是,它们从未被实例化,也就是说,它们从未得到值。统一两个变量可以实例化它们,但这不是必需的。例如,您可以在prolog REPL中运行此命令:

?- N = N1.
N = N1.
虽然
N
N1
没有值(但),但它们是统一的;如果N1稍后被实例化,N也将被实例化为相同的值。另一个不那么琐碎的例子:

?- [H|T] = L, L = [1|M], writeln(H).
1
H = 1,
T = M,
L = [1|M].
然而,
N
确实从未与
N1+1
统一
is
将评估
N1+1
值将与
N
统一。这将在内部
大小([b,c,d],N1)
被计算后发生,因此
N1
变量将被实例化

基本上,执行过程如下所示:

size([a,b,c,d],N).
          size([b,c,d],N1)
               size([c,d],N1)
                   size([d],N1)
                        size([],0)
                        N is 0+1.
                   N is 1+1.
               N is 2+1.
          N is 3+1.
size([],0).
size([_|T], 1+ N1):-size(T,N1).
这有点低效,因为我们需要将所有函数调用保存在内存中;查看尾部递归和累加器以解决此问题

还要注意,
N1
只需要实例化,因为
is
将对表达式求值。你可以这样写:

size([a,b,c,d],N).
          size([b,c,d],N1)
               size([c,d],N1)
                   size([d],N1)
                        size([],0)
                        N is 0+1.
                   N is 1+1.
               N is 2+1.
          N is 3+1.
size([],0).
size([_|T], 1+ N1):-size(T,N1).
如果你叫它:

?- size([1,2],N).
N = 0+1+1.

很有趣,不是吗?您可能希望计算最后的N,例如,将其称为
size([1,2],N),结果为N
。然而,一个问题是,我们在内存中保留了一个
0+1+1+1+1+…
链,它可以很快变大。因此,最好将此技巧用于不会增长的内容,例如表达式模板。

N
在计算
时肯定是统一的,因为
从左到右执行,递归调用通过统一(的本地版本)最终绑定
N
请使用
0
。感谢您的快速响应。我不知道我是否明白你的意思。在跟踪过程中,其中N与0统一?我纠正自己:
N1
首先统一,要么与
0
统一,要么与子句
size([H | T],N)
中的较低级别
N
统一,然后使用
N1
计算顶层
N
。诀窍是要理解在递归算法中,有几个不同版本的
N
,每个版本都有不同的值。在您的示例中,这些不同的
N
s将绑定到0、1、2、3和4。实际上,后一个变量是尾部递归的!有效率的不,但有趣的是…@重复是的,我认为这是一个很好的方式来显示尾部递归版本和常规版本之间的差异,而不必解释累加器;然后,带累加器的版本可以作为优化算术运算的一种方式呈现。再想一想,您可能希望通过将
size/2
的第二个子句更改为
size([[u| Es],1+N]:-size(Es,N)来将表达式从左倾调整为右倾!至少在SWI中,左倾和右倾的区别是巨大的。当然,这取决于
(is)/2
…@repeat的内部实现非常有趣,
N+1
几乎需要一秒钟的时间,而
1+N
是即时的。它还以6m的元素而不是11m的元素耗尽堆栈(我也在使用SWI prolog)。