我知道用正确的函数结构编写没有错,但我想知道如何用最 Pythonic 的方式用一条线找到第 n 个斐波那契数。
我编写了该代码,但在我看来这不是最好的方法:
>>> fib = lambda n:reduce(lambda x, y: (x[0]+x[1], x[0]), [(1,1)]*(n-2))[0]
>>> fib(8)
13
怎样才能更好更简单?
fib = lambda n:reduce(lambda x,n:[x[1],x[0]+x[1]], range(n),[0,1])[0]
(这维护了一个从 [a,b] 映射到 [b,a+b] 的元组,初始化为 [0,1],迭代 N 次,然后取第一个元组元素)
>>> fib(1000)
43466557686937456435688527675040625802564660517371780402481729089536555417949051
89040387984007925516929592259308032263477520968962323987332247116164299644090653
3187938298969649928516003704476137795166849228875L
(请注意,在此编号中,fib(0) = 0、fib(1) = 1、fib(2) = 1、fib(3) = 2 等)
(另请注意:reduce
是 Python 2.7 中的内置函数,但不是 Python 3 中的内置函数;您需要from functools import reduce
在 Python 3 中执行。)
一个很少见的技巧是 lambda 函数可以递归地引用自身:
fib = lambda n: n if n < 2 else fib(n-1) + fib(n-2)
顺便说一句,它很少见,因为它令人困惑,而且在这种情况下它也是低效的。最好把它写在多行上:
def fibs():
a = 0
b = 1
while True:
yield a
a, b = b, a + b
我最近学习了使用矩阵乘法来生成斐波那契数,这很酷。你取一个基本矩阵:
[1, 1]
[1, 0]
并将其自身相乘 N 次得到:
[F(N+1), F(N)]
[F(N), F(N-1)]
今天早上,在淋浴墙上的蒸汽中涂鸦,我意识到你可以通过从第二个矩阵开始将运行时间减半,然后将其乘以 N/2 次,然后使用 N 从第一个矩阵中选择一个索引行列。
稍微挤压一下,我把它归结为一行:
import numpy
def mm_fib(n):
return (numpy.matrix([[2,1],[1,1]])**(n//2))[0,(n+1)%2]
>>> [mm_fib(i) for i in range(20)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
这是使用整数运算的斐波那契数列的封闭表达式,并且非常有效。
fib = lambda n:pow(2<<n,n+1,(4<<2*n)-(2<<n)-1)%(2<<n)
>> fib(1000)
4346655768693745643568852767504062580256466051737178
0402481729089536555417949051890403879840079255169295
9225930803226347752096896232398733224711616429964409
06533187938298969649928516003704476137795166849228875L
它以 O(log n) 算术运算计算结果,每个运算都作用于具有 O(n) 位的整数。鉴于结果(第 n 个斐波那契数)为 O(n) 位,该方法非常合理。
它基于genefib4
来自http://fare.tunes.org/files/fun/fibonacci.lisp,而后者又基于我的一个效率较低的封闭形式整数表达式(参见:http://paulhankin.github。 io/斐波那契/ )
如果我们认为“最 Pythonic 的方式”是优雅和有效的,那么:
def fib(nr):
return int(((1 + math.sqrt(5)) / 2) ** nr / math.sqrt(5) + 0.5)
赢得双手。当您可以通过使用黄金比例近似结果在 O(1) 中很好地解决问题时,为什么要使用效率低下的算法(如果您开始使用记忆化,我们可以忘记 oneliner)?虽然实际上我显然会以这种形式写它:
def fib(nr):
ratio = (1 + math.sqrt(5)) / 2
return int(ratio ** nr / math.sqrt(5) + 0.5)
更高效,更容易理解。
这是一个非递归(匿名)记忆一个班轮
fib = lambda x,y=[1,1]:([(y.append(y[-1]+y[-2]),y[-1])[1] for i in range(1+x-len(y))],y[x])[1]
fib = lambda n, x=0, y=1 : x if not n else fib(n-1, y, x+y)
运行时间 O(n),fib(0) = 0,fib(1) = 1,fib(2) = 1 ...
我是 Python 新手,但出于学习目的做了一些测量。我收集了一些 fibo 算法并采取了一些措施。
from datetime import datetime
import matplotlib.pyplot as plt
from functools import wraps
from functools import reduce
from functools import lru_cache
import numpy
def time_it(f):
@wraps(f)
def wrapper(*args, **kwargs):
start_time = datetime.now()
f(*args, **kwargs)
end_time = datetime.now()
elapsed = end_time - start_time
elapsed = elapsed.microseconds
return elapsed
return wrapper
@time_it
def fibslow(n):
if n <= 1:
return n
else:
return fibslow(n-1) + fibslow(n-2)
@time_it
@lru_cache(maxsize=10)
def fibslow_2(n):
if n <= 1:
return n
else:
return fibslow_2(n-1) + fibslow_2(n-2)
@time_it
def fibfast(n):
if n <= 1:
return n
a, b = 0, 1
for i in range(1, n+1):
a, b = b, a + b
return a
@time_it
def fib_reduce(n):
return reduce(lambda x, n: [x[1], x[0]+x[1]], range(n), [0, 1])[0]
@time_it
def mm_fib(n):
return (numpy.matrix([[2, 1], [1, 1]])**(n//2))[0, (n+1) % 2]
@time_it
def fib_ia(n):
return pow(2 << n, n+1, (4 << 2 * n) - (2 << n)-1) % (2 << n)
if __name__ == '__main__':
X = range(1, 200)
# fibslow_times = [fibslow(i) for i in X]
fibslow_2_times = [fibslow_2(i) for i in X]
fibfast_times = [fibfast(i) for i in X]
fib_reduce_times = [fib_reduce(i) for i in X]
fib_mm_times = [mm_fib(i) for i in X]
fib_ia_times = [fib_ia(i) for i in X]
# print(fibslow_times)
# print(fibfast_times)
# print(fib_reduce_times)
plt.figure()
# plt.plot(X, fibslow_times, label='Slow Fib')
plt.plot(X, fibslow_2_times, label='Slow Fib w cache')
plt.plot(X, fibfast_times, label='Fast Fib')
plt.plot(X, fib_reduce_times, label='Reduce Fib')
plt.plot(X, fib_mm_times, label='Numpy Fib')
plt.plot(X, fib_ia_times, label='Fib ia')
plt.xlabel('n')
plt.ylabel('time (microseconds)')
plt.legend()
plt.show()
具有递归和缓存的 Fiboslow_2、Fib 整数算术和 Fibfast 算法似乎是最好的。也许我的装饰器不是衡量性能的最佳选择,但总体来说它看起来不错。
另一个例子,从马克拜尔斯的回答中得到启示:
fib = lambda n,a=0,b=1: a if n<=0 else fib(n-1,b,a+b)
我想看看我是否可以创建一个完整的序列,而不仅仅是最终值。
下面将生成一个长度为 100 的列表。它不包括前导[0, 1]
并且适用于 Python2 和 Python3。除了这一行,没有其他行了!
(lambda i, x=[0,1]: [(x.append(x[y+1]+x[y]), x[y+1]+x[y])[1] for y in range(i)])(100)
输出
[1,
2,
3,
...
218922995834555169026,
354224848179261915075,
573147844013817084101]
这是一个不使用递归的实现,只记住最后两个值而不是整个序列历史。
下面的 nthfib() 是原始问题的直接解决方案(只要允许导入)
它不如使用上述 Reduce 方法优雅,但是,尽管与所要求的略有不同,但如果还需要输出直到第 n 个数字的序列,它就可以更有效地用作无限生成器(稍微重写为下面的 fibgen())。
from itertools import imap, islice, repeat
nthfib = lambda n: next(islice((lambda x=[0, 1]: imap((lambda x: (lambda setx=x.__setitem__, x0_temp=x[0]: (x[1], setx(0, x[1]), setx(1, x0_temp+x[1]))[0])()), repeat(x)))(), n-1, None))
>>> nthfib(1000)
43466557686937456435688527675040625802564660517371780402481729089536555417949051
89040387984007925516929592259308032263477520968962323987332247116164299644090653
3187938298969649928516003704476137795166849228875L
from itertools import imap, islice, repeat
fibgen = lambda:(lambda x=[0,1]: imap((lambda x: (lambda setx=x.__setitem__, x0_temp=x[0]: (x[1], setx(0, x[1]), setx(1, x0_temp+x[1]))[0])()), repeat(x)))()
>>> list(islice(fibgen(),12))
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
def fib(n):
x =[0,1]
for i in range(n):
x=[x[1],x[0]+x[1]]
return x[0]
从 Jason S 那里得到启发,我认为我的版本有更好的理解。
开始Python 3.8
,并引入赋值表达式(PEP 572)(:=
运算符),我们可以在列表推导中使用和更新变量:
fib = lambda n,x=(0,1):[x := (x[1], sum(x)) for i in range(n+1)][-1][0]
这:
n-1
并n-2
作为元组x=(0, 1)
n
时间的一部分,x
通过赋值表达式( x := (x[1], sum(x))
) 更新为新的n-1
和n-2
值x
为了解决这个问题,我受到 Stackoverflow Single Statement Fibonacci中的一个类似问题的启发,我得到了这个可以输出斐波那契序列列表的单行函数。不过,这是一个 Python 2 脚本,未在 Python 3 上测试:
(lambda n, fib=[0,1]: fib[:n]+[fib.append(fib[-1] + fib[-2]) or fib[-1] for i in range(n-len(fib))])(10)
将此 lambda 函数分配给一个变量以重用它:
fib = (lambda n, fib=[0,1]: fib[:n]+[fib.append(fib[-1] + fib[-2]) or fib[-1] for i in range(n-len(fib))])
fib(10)
输出是斐波那契数列的列表:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
我不知道这是否是最 Pythonic 的方法,但这是我能想到的最好的方法:->
Fibonacci = lambda x,y=[1,1]:[1]*x if (x<2) else ([y.append(y[q-1] + y[q-2]) for q in range(2,x)],y)[1]
上面的代码没有使用递归,只是一个存储值的列表。
我的 2 美分
# One Liner
def nthfibonacci(n):
return long(((((1+5**.5)/2)**n)-(((1-5**.5)/2)**n))/5**.5)
或者
# Steps
def nthfibonacci(nth):
sq5 = 5**.5
phi1 = (1+sq5)/2
phi2 = -1 * (phi1 -1)
n1 = phi1**(nth+1)
n2 = phi2**(nth+1)
return long((n1 - n2)/sq5)
为什么不使用列表推导?
from math import sqrt, floor
[floor(((1+sqrt(5))**n-(1-sqrt(5))**n)/(2**n*sqrt(5))) for n in range(100)]
没有数学导入,但不那么漂亮:
[int(((1+(5**0.5))**n-(1-(5**0.5))**n)/(2**n*(5**0.5))) for n in range(100)]
相似的:
def fibonacci(n):
f=[1]+[0]
for i in range(n):
f=[sum(f)] + f[:-1]
print f[1]
使用递归的简单斐波那契数生成器
fib = lambda x: 1-x if x < 2 else fib(x-1)+fib(x-2)
print fib(100)
这需要永远fib(100)
在我的电脑中计算。
斐波那契数列也有封闭形式。
fib = lambda n: int(1/sqrt(5)*((1+sqrt(5))**n-(1-sqrt(5))**n)/2**n)
print fib(50)
由于精度问题,这几乎可以处理 72 个数字。
带有逻辑运算符的 Lambda
fibonacci_oneline = lambda n = 10, out = []: [ out.append(i) or i if i <= 1 else out.append(out[-1] + out[-2]) or out[-1] for i in range(n)]
这是我的做法,但是该函数为列表理解行部分返回 None 以允许我在其中插入一个循环..所以基本上它所做的是将 fib seq 的新元素附加到一个超过两个元素的列表中
>>f=lambda list,x :print('The list must be of 2 or more') if len(list)<2 else [list.append(list[-1]+list[-2]) for i in range(x)]
>>a=[1,2]
>>f(a,7)
您可以生成一次包含一些值的列表并根据需要使用:
fib_fix = []
fib = lambda x: 1 if x <=2 else fib_fix[x-3] if x-2 <= len(fib_fix) else (fib_fix.append(fib(x-2) + fib(x-1)) or fib_fix[-1])
fib_x = lambda x: [fib(n) for n in range(1,x+1)]
fib_100 = fib_x(100)
比例如:
a = fib_fix[76]
import math
sqrt_five = math.sqrt(5)
phi = (1 + sqrt_five) / 2
fib = lambda n : int(round(pow(phi, n) / sqrt_five))
print([fib(i) for i in range(1, 26)])
单行 lambda fibonacci 但有一些额外的变量