我该如何用列表推导式创建斐波那契数列?

16

我是Python新手,想知道是否可以使用Python的列表推导式功能生成斐波那契数列。我不知道列表推导式是如何实现的。 我尝试了以下代码(意图是生成前五个斐波那契数):

series=[]
series.append(1)
series.append(1)
series += [series[k-1]+series[k-2] for k in range(2,5)]

这段代码会抛出错误:IndexError: list index out of range

如果有可能使用列表推导式生成这样的系列,请告诉我。


1
你不能这样做,因为列表推导式在添加到series之前会先被评估... - Willem Van Onsem
reduce 对于斐波那契数列来说是更好的选择,因为迭代 X 的输入取决于迭代 X-1 的输出。 - Alex Fung
@AlexFung 有任何例子吗?谢谢。 - Tirbo06
13个回答

13

你不能这样做: 列表推导式先被执行,然后将该列表添加到series中。实际上这就相当于你写了以下内容:

series=[]
series.append(1)
series.append(1)
<b>temp = </b>[series[k-1]+series[k-2] for k in range(2,5)]
series<b> += temp</b>

你可以使用列表推导式强制产生副作用以解决这个问题,例如:

series=[]
series.append(1)
series.append(1)
<b>[series.append(series[k-1]+series[k-2]) for k in range(2,5)]</b>

请注意,这里不会将结果添加到序列中。列表推导式仅用于调用.appendseries上。然而,一些人认为具有副作用的列表推导式容易出错:它不是很声明性,并且如果不小心处理可能会引入错误。


1
使用Python3.8中的赋值表达式,您可以绕过具有副作用的列表推导式来创建一个新的斐波那契数列列表。是否比使用副作用(就像您在这里所做的那样)更好,这是有争议的。 - cs95
这会创建另一个列表。我宁愿使用普通函数。 - chaulap

10

我们可以使用 Python 的列表推导式(或生成器),利用它与黄金比例的关系来编写一个简洁的代码:

>>> series = [int((((1 + 5**0.5) / 2)**n - ((1 - 5**0.5) / 2)**n) / 5**0.5) for n in range(1, 21)]
>>> series
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
>>> 

或者稍微更加友好地表达为:

>>> square_root_of_five = 5**0.5
>>> Phi = (1 + square_root_of_five) / 2
>>> phi = (1 - square_root_of_five) / 2
>>> 
>>> series = [int((Phi**n - phi**n) / square_root_of_five) for n in range(1, 21)]
>>> series
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]

8
如果您知道需要多少个系列项,那么可以像这样紧凑地编写代码,而不需要使用列表推导式。
def Fibonacci(n):
    f0, f1 = 1, 1
    for _ in range(n):
        yield f0
        f0, f1 = f1, f0+f1

fibs = list(Fibonacci(10))
print (fibs)

如果您想要一些不确定数量的术语,那么可以使用以下方法,这与之前的方法非常相似。
def Fibonacci():
    f0, f1 = 1, 1
    while True:
        yield f0
        f0, f1 = f1, f0+f1

fibs = []
for f in Fibonacci():
    fibs.append(f)
    if f>100:
        break
print (fibs)

当您需要一个可能是无限集合的项目时,您应该考虑使用一个带有一个或多个yield语句的函数或生成器表达式。我想用生成器表达式来生成斐波那契数列,但显然不行。

8

使用赋值表达式 (python >= 3.8):

s = [0, 1]
s += [(s := [s[1], s[0] + s[1]]) and s[1] for k in range(10)]

print (s)
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

5

接着Willem van Onsem所说:

计算斐波那契数列的第n项的传统方法是将n-1n-2项相加,您已经知道了。列表推导式的设计目的是在推导期间创建一个没有副作用(除了创建单个列表)的列表。在计算序列期间存储最后2项是一种副作用,因此,仅使用列表推导式无法完成任务。

解决这个问题的安全方法是制作闭包生成器(本质上是带有一些相关私有状态的生成器),可以将其传递给列表推导式,使得列表推导式不必担心正在存储的详细信息:

def fib_generator(n):

    def fib_n_generator():
        last = 1
        curr = 1

        if n == 0:
            return

        yield last
        if n == 1:
            return

        yield curr
        if n == 2:
            return

        ii = 2
        while ii < n:
            next = curr + last
            yield next
            last = curr
            curr = next
            ii += 1

    return fib_n_generator()

fib = [xx for xx in fib_generator(10)]
print(fib)

感谢第一段中的解释,指出“在计算序列期间存储最后2个项是一种副作用,因此仅使用列表推导式不适合完成该任务”。 - Iqra.
然而,即使花费了15分钟以上的时间,我仍然无法理解在上面的代码片段中使用yield的好处。如果fib_generator(n)是一个没有生成器的函数,fib = [xx for xx in fib_generator(10)]仍然会被调用。 - Iqra.
1
关键是yield,否则它就不是生成器。没有yield,fib_n_generator()只会返回一个东西,而不是一系列的东西。我本可以让我的答案更简单:我不需要嵌套函数,所以它应该像Bill Bell的答案一样(这就是为什么他的赞更多;-))。我也可以重写它,返回一个列表而不是生成器,但那将违背使用生成器的主要目的,即避免不必要的RAM使用。 - daphtdazz

1
这里提供了一个一行代码的列表推导式解决方案,避免了嵌套 三元运算符海象运算符 的分离初始化步骤(所以需要 Python 3.8),同时也避免了 显式形式 可能会带来的快速溢出问题(由其 **n 组成)。
[
    0 if not i else
        (x := [0, 1]) and 1 if i == 1 else
            not x.append(x[-2] + x[-1]) and x[-1]
    for i in range(10)
]

给:

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

这比使用显式形式生成所有小于N的值更快。然而,如果您不需要所有的值,那么显式形式可能会更快,但对于1000到2000之间的一些N,它会遭受溢出的问题:

n = 2000
int((((1 + 5**0.5) / 2)**n - ((1 - 5**0.5) / 2)**n) / 5**0.5)

给我提供:
OverflowError: (34, 'Numerical result out of range')

与“添加最后两个值”的方法相比,该方法可以为较大的N生成更高的值。在我的电脑上,在运行内存耗尽之前,我可以一直进行到300000和400000之间的某个N。

感谢Jonathan Gregory为我提供了大部分实现此方法的帮助。


哇,我不知道这个,谢谢分享!看起来对我来说是正确的答案! - Tirbo06
更简单(或至少更短)的写法是:series = [i0 := 0, i1 := 1]+[i1 := i0 + (i0 := i1) for j in range(2,5)] - Camion

0
我是这样做的:
def Phi(number:int):
n = [1,1]
[n.append(n[i-2]+n[i-1])for i in range(2,number)]
return n

0

简化版@dhassel版本(需要Python 3.8或更高版本)

series = [i0 := 0, i1 := 1]+[i1 := i0 + (i0 := i1) for j in range(2, 5)]

可以将One写成生成器表达式,但有点棘手,因为由于某种原因,显而易见的答案:fibo = (v for g in ((i0 := 0, i1 := 1), (i1 := i0 + (i0 := i1) for j in range(2,10))) for v in g)不起作用(我不排除有错误)。但是,如果您在外部获取子生成器列表,则可以正常工作:

glist = ((i0 := 0, i1 := 1), (i1 := i0 + (i0 := i1) for j in range(2, 5)))
fibo = (v for g in glist for v in g)

0
# Get a number from the user.
number = int(input("enter a number"))

# Create a empty list
mylist=[] 

# create list comprehension following fibonaci series
[mylist.append(0)  if n==0 else mylist.append(1)  if n==1 else mylist.append(mylist[-2]+mylist[-1]) for n in range(number+1)]

print(mylist)  

0

基于显式公式1的斐波那契数列列表推导:

[int((0.5+5**0.5/2)**n/5**0.5+0.5) for n in range(21)]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接