思路:如果要一行代码实现定义,通常会想到用匿名函数(lambda表达式)。斐波那契数列是递归定义的,这里就涉及到匿名函数如何调用自身的问题。基于定义,可以直接想到以下最直观的写法:
fib = lambda n: 1 if n <= 1 else fib(n-1) + fib(n-2)
其他答主也提到,这个实现的时间复杂度会很高(其实是指数级别的)。而通过优化可以把时间复杂度降低到线性, @吴扬 的回答就是如如此:
fib = lambda n, a=1, b=1: a if n == 0 else fib(n-1, b, a+b)
但以上两个答案都有共同的局限性:它们都调用了一个被命名为“fib”的函数。但是,如果函数没有名字,就无法调用它自身。现在将lambda表达式赋值给一个变量fib,相当于“取消”了匿名函数的匿名性,才能实现递归函数“调用自身”的操作。我们可以用类似fib(5)的方法调用这个函数,但如果我们想要将函数的定义和调用都在一行代码中完成,采用类似(lambda x: x + 1)(3)这种方式,定义依然是匿名的,应该怎么做呢?
答案是,把函数本身作为它自身的一个参数:
lambda n, fib: 1 if n <= 1 else fib(n-1, fib) + fib(n-2, fib)
这样做,就相当于在每次递归调用时,都“保留”了这个函数的定义。如果想要在一行代码里调用这个函数求第N项斐波那契数列的值,只需要将这个匿名函数本身作为调用的一个参数传入:
(lambda n, fib: 1 if n <= 1 else fib(n-1, fib) + fib(n-2, fib))(N, lambda n, fib: 1 if n <= 1 else fib(n-1, fib) + fib(n-2, fib))
上面这行代码非常绕,如果我们把函数的定义,即
lambda n, fib: 1 if n <= 1 else fib(n-1, fib) + fib(n-2, fib)
替换成字符串F,那么上述的调用就相当于:
(F)(N, F)
然后,我们为函数的fib参数提供一个默认值,这样的话,就能避免调用函数时额外传入一个参数:
lambda n, fib=(lambda n, fib: 1 if n <= 1 else fib(n-1, fib) + fib(n-2, fib)): 1 if n <= 1 else fib(n-1, fib) + fib(n-2, fib)
如果采用线性时间复杂度的定义,再为匿名函数的参数fib设置一个默认值,它会长得像这样:
lambda n, a=1, b=1, fib=(lambda n, a, b, fib: a if n == 0 else fib(n-1, b, a+b, fib)): a if n == 0 else fib(n-1, b, a+b, fib)
到这一步才算大功告成,我们就可以在一行代码中实现斐波那契数列的定义和调用。。。
(lambda n, a=1, b=1, fib=(lambda n, a, b, fib: a if n == 0 else fib(n-1, b, a+b, fib)): a if n == 0 else fib(n-1, b, a+b, fib))(6) # 13
要输出从第一项开始的数列的值也很容易了:
[(lambda n, a=1, b=1, fib=(lambda n, a, b, fib: a if n == 0 else fib(n-1, b, a+b, fib)): a if n == 0 else fib(n-1, b, a+b, fib))(i) for i in range(10)]
# [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
这个答案是看了有关不动点组合子的知识后想到的。继续阅读:Fixed-point Combinatoren.wikipedia.org