【问题标题】:Fibonacci numbers, with an one-liner in Python 3?斐波那契数,在 Python 3 中使用单线?
【发布时间】:2011-06-23 14:03:09
【问题描述】:

我知道用正确的函数结构编写没有什么问题,但我想知道如何用最 Pythonic 的方式用一条线找到第 n 个斐波那契数。

我写了那段代码,但在我看来这不是最好的方法:

>>> fib = lambda n:reduce(lambda x, y: (x[0]+x[1], x[0]), [(1,1)]*(n-2))[0]
>>> fib(8)
13

如何更好更简单?

【问题讨论】:

  • 你是真的用 python 3 还是只用 python? (顺便说一句,兴趣+1ng 练习)

标签: python fibonacci


【解决方案1】:

我想看看我是否可以创建一个完整的序列,而不仅仅是最终值。

以下将生成长度为 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]

【讨论】:

    【解决方案2】:
    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 但有一些额外的变量

    【讨论】:

      【解决方案3】:

      Python 3.8 开始,并引入assignment expressions (PEP 572):= 运算符),我们可以在列表解析中使用和更新变量:

      fib = lambda n,x=(0,1):[x := (x[1], sum(x)) for i in range(n+1)][-1][0]
      

      这个:

      • n-1n-2 作为一个元组启动x=(0, 1)
      • 作为循环n 次的列表理解的一部分,x 通过赋值表达式 (x := (x[1], sum(x))) 更新为新的n-1n-2
      • 最后,我们从上一次迭代中返回,x 的第一部分

      【讨论】:

        【解决方案4】:

        您可以生成一次包含一些值的列表并根据需要使用:

        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]
        

        【讨论】:

          【解决方案5】:
          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 中的内置函数;您需要在 Python 3 中执行 from functools import reduce。)

          【讨论】:

          • 但是没真正看懂解法,x是[0,1]+range(n)中的一个整数,对吧(我想我的错在这里)?但是我们使用 x[1],x[0]。如何?我看不出我们如何维护一个元组。
          • reduce 的输入函数有两个参数,一个累加器和一个输入:reduce 为迭代中的每个元素调用该函数(在本例中为 range(n)。) case 是x,它是一个元组,初始化为[0,1]。 reduce() 中的函数输出一个新的元组[x[1],x[0]+x[1]]
          【解决方案6】:

          我是 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 算法似乎是最好的。也许我的装饰器不是衡量性能的最佳选择,但总体来说它似乎不错。

          【讨论】:

            【解决方案7】:

            为什么不使用列表推导式?

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

            【讨论】:

              【解决方案8】:

              这是使用整数运算的斐波那契数列的封闭表达式,效率很高。

              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) 位,该方法是相当合理的。

              它基于来自http://fare.tunes.org/files/fun/fibonacci.lispgenefib4,而后者又基于我的一个效率较低的封闭形式整数表达式(参见:http://paulhankin.github.io/Fibonacci/

              【讨论】:

                【解决方案9】:

                我是这样做的,但是该函数为列表理解行部分返回 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)
                

                【讨论】:

                  【解决方案10】:

                  带有逻辑运算符的 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)]
                  

                  【讨论】:

                    【解决方案11】:

                    使用递归的简单斐波那契数生成器

                    fib = lambda x: 1-x if x < 2 else  fib(x-1)+fib(x-2)
                    print fib(100)
                    

                    这需要很长时间才能在我的计算机中计算 fib(100)

                    还有closed form 的斐波那契数。

                    fib = lambda n: int(1/sqrt(5)*((1+sqrt(5))**n-(1-sqrt(5))**n)/2**n)
                    print fib(50)
                    

                    由于精度问题,这几乎可以处理 72 个数字。

                    【讨论】:

                      【解决方案12】:

                      我的 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)
                      

                      【讨论】:

                        【解决方案13】:

                        我最近学习了使用矩阵乘法来生成斐波那契数,这很酷。你取一个基本矩阵:

                        [1, 1]
                        [1, 0]
                        

                        将它自己乘N次得到:

                        [F(N+1), F(N)]
                        [F(N), F(N-1)]
                        

                        今天早上,在淋浴墙上涂鸦时,我意识到你可以将运行时间减半从第一行/列开始。

                        稍微挤压一下,我把它归为一行:

                        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]
                        

                        【讨论】:

                        • @winmwx:Numpy 数组支持 2D 索引 ([i,j]),所以对于普通列表,它就像 a[0][(1,0)[n%2]] .它基本上是获取矩阵的第一行 ([F(N+1), F(N)]),然后使用 (1,0)[n%2] 根据是否 N 来选择这两个中的哪一个是偶数还是奇数。因此,如果 N 为偶数,则取第二个 (F(N)),如果为奇数,则取第一个 (F(N+1))。现在我再看一遍,我可以让它使用 [0, (n+1)%2]。由于我们从第二个矩阵 ([[2,1],[1,1]]) 开始,它差了一个。
                        【解决方案14】:

                        我不知道这是否是最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]
                        

                        上面的代码没有使用递归,只是一个列表来存储值。

                        【讨论】:

                          【解决方案15】:

                          类似的:

                              def fibonacci(n):
                                f=[1]+[0]
                                for i in range(n):
                                  f=[sum(f)] + f[:-1]
                                print f[1]
                          

                          【讨论】:

                            【解决方案16】:
                            def fib(n):
                                x =[0,1]
                                for i in range(n):
                                    x=[x[1],x[0]+x[1]]
                                return x[0]
                            

                            从 Jason S 那里得到启发,我认为我的版本有更好的理解。

                            【讨论】:

                            • 这个问题明确要求单行,你读过这个吗?
                            【解决方案17】:

                            为了解决这个问题,我受到 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]
                            

                            【讨论】:

                              【解决方案18】:

                              这是一个非递归(匿名)记忆一个衬里

                              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]
                              

                              【讨论】:

                                【解决方案19】:

                                这是一个不使用递归的实现,只记住最后两个值而不是整个序列历史。

                                下面的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]
                                

                                【讨论】:

                                • 单线,但长线 ;-)
                                【解决方案20】:
                                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 ...

                                【讨论】:

                                  【解决方案21】:

                                  另一个例子,从 Mark Byers 的回答中得到启示:

                                  fib = lambda n,a=0,b=1: a if n<=0 else fib(n-1,b,a+b)
                                  

                                  【讨论】:

                                  • ...虽然它似乎在 n=999 处存在递归深度问题。 Python 没有尾递归吗?
                                  • 不,它没有尾递归消除
                                  【解决方案22】:

                                  如果我们认为“最 Pythonic 的方式”优雅且有效,那么:

                                  def fib(nr):
                                      return int(((1 + math.sqrt(5)) / 2) ** nr / math.sqrt(5) + 0.5)
                                  

                                  胜出。当您可以通过使用黄金比例近似结果在 O(1) 中很好地解决问题时,为什么要使用效率低下的算法(如果您开始使用记忆化,我们可以忘记单线)?虽然实际上我显然会以这种形式写它:

                                  def fib(nr):
                                      ratio = (1 + math.sqrt(5)) / 2
                                      return int(ratio ** nr / math.sqrt(5) + 0.5)
                                  

                                  更高效,更容易理解。

                                  【讨论】:

                                  • 我也想过显式的斐波那契公式,但是对于大的n来说它有精度问题。
                                  • small n 存在精度问题; fib(71) 是错误的。如果我们只要求前几项是正确的,那么 def fib(n): return [0, 1, 1, 2, 3, ..][n] 甚至更简单.. [更新以解决变化代码中从 round 到 int。]
                                  • 谢谢,其实我的主要目的是探索Python的能力,而不是快速计算:)。 +1
                                  • 好吧,这是目光短浅的——只测试了前 60 个值,并假设如果它在那里工作,我们不会遇到更大值的精度问题。好吧,就这么多。是的,改变了公式,因为我认为没有明确的四舍五入它应该可以正常工作。
                                  • 为什么你认为memoization 排除了单行列?
                                  【解决方案23】:

                                  一个很少见的技巧是 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->n, 2->1 允许 fib(0) = 0。
                                  • @Jason S, @DSM:感谢 cmets。更新了答案。
                                  • +1 用于生成器,我一直认为它是 Python 中表示斐波那契数列最优雅、最有效的方式。
                                  • +1 适用于 fib(0),fib(1),fib(2) 与 OP 不同
                                  猜你喜欢
                                  • 2011-09-19
                                  • 2014-05-23
                                  • 1970-01-01
                                  • 2020-04-19
                                  • 1970-01-01
                                  • 2013-08-03
                                  • 2013-02-24
                                  • 1970-01-01
                                  • 1970-01-01
                                  相关资源
                                  最近更新 更多