【问题标题】:Fibonacci numbers generator in Swift 3Swift 3 中的斐波那契数字生成器
【发布时间】:2017-03-05 07:57:16
【问题描述】:

以下问答涵盖了在 Swift 中生成斐波那契数的几种方法,但它已经过时了(Swift 1.2?):

问题:我们如何使用现代 Swift (Swift >= 3) 巧妙地生成斐波那契数?最好是避免显式递归的方法。

【问题讨论】:

    标签: swift swift3 sequence fibonacci


    【解决方案1】:

    使用全局sequence(state:next:)函数

    斯威夫特 3.0

    作为替代方案,我们可以使用一个简洁的全局 sequence 函数,这是在 Swift 3.0 中实现的一对函数(如进化提案 SE-0094 中所述)。

    使用后者,我们可以在sequence(state:next:)next 闭包中将斐波那契数列的先前和当前状态保留为可变的state 属性。

    func fibs(through: Int, includingZero useZero: Bool = false)
        -> UnfoldSequence<Int, (Int, Int)> {
        return sequence(state: useZero ? (1, 0) : (0, 1),
                        next: { (pair: inout (Int, Int)) -> Int? in
            guard pair.1 <= through else { return nil }
            defer { pair = (pair.1, pair.0 + pair.1) }
            return pair.1
            })
    }
        // explicit type annotation of inout parameter closure
        // needed due to (current) limitation in Swift's type
        // inference
    
    // alternatively, always start from one: drop useZero 
    // conditional at 'state' initialization
    func fibs1(through: Int)
        -> UnfoldSequence<Int, (Int, Int)> {
        return sequence(state: (0, 1),
                        next: { (pair: inout (Int, Int)) -> Int? in
            guard pair.1 <= through else { return nil }
            defer { pair = (pair.1, pair.0 + pair.1) }
            return pair.1
            })
    }
    

    或者,使用 tuple hacks 来压缩它(但是执行 next 一个额外的、不必要的时间)

    func fibs(through: Int, includingZero useZero: Bool = false) -> UnfoldSequence<Int, (Int, Int)> {
        return sequence(state: useZero ? (1, 0) : (0, 1), next: { 
            ($0.1 <= through ? $0.1 : Optional<Int>.none, $0 = ($0.1, $0.0 + $0.1)).0 })
    }
    
    func fibs1(through: Int) -> UnfoldSequence<Int, (Int, Int)> {
        return sequence(state: (0, 1), next: { 
            ($0.1 <= through ? $0.1 : Optional<Int>.none, $0 = ($0.1, $0.0 + $0.1)).0 })
    }
    

    请注意,当不再满足 ... &lt;= through 条件时,我们会以 nil 返回显式终止序列。

    示例用法:

    // fib numbers up through 50, excluding 0
    fibs(through: 50).forEach { print($0) }
        // 1 1 2 3 5 8 13 21 34
    
    // ... or
    fibs1(through: 50).forEach { print($0) }
        // 1 1 2 3 5 8 13 21 34
    
    // ... including 0
    fibs(through: 50, includingZero: true).forEach { print($0) }
        // 0 1 1 2 3 5 8 13 21 34
    
    // project Euler #2: sum of even fib numbers up to 4000000
    print(fibs(through: 4_000_000)
        .reduce(0) { $1 % 2 == 0 ? $0 + $1 : $0 }) // 4 613 732
    

    我们还可以从上面删除终止条件来构造一个无限的斐波那契数序列,以组合使用,例如prefix:

    func infFibs() -> UnfoldSequence<Int, (Int, Int)> {
        return sequence(state: (0, 1), next: {
            (pair: inout (Int, Int)) -> Int in (pair.1, pair = (pair.1, pair.0 + pair.1)).0 })
    }
    
    // prefix the first 6 fib numbers (excluding 0) from
    // the infinite sequence of fib numbers
    infFibs().prefix(10).forEach { print($0) }
        // 1 1 2 3 5 8 13 21 34 55
    

    斯威夫特 3.1

    当 Swift 3.1 到来时,序列的 prefix(while:) 方法,如进化提案 SE-0045 中所述,将已经实现。使用这个附加功能,我们可以修改上面的fibs 方法来避免显式的 by-nil 条件序列终止:

    func fibs(through: Int, startingFromZero useZero: Bool = false)
        -> AnySequence<Int> {
        return sequence(state: useZero ? (1, 0) : (0, 1),
                        next: { (pair: inout (Int, Int)) -> Int? in
            defer { pair = (pair.1, pair.0 + pair.1) }
            return pair.1
            }).prefix(while: { $0 <= through })
    }
    
    // alternatively, always start from one: drop useZero 
    // conditional at 'state' initialization
    func fibs1(through: Int) -> AnySequence<Int> {
        return sequence(state: (0, 1),
                        next: { (pair: inout (Int, Int)) -> Int? in
            defer { pair = (pair.1, pair.0 + pair.1) }
            return pair.1
            }).prefix(while: { $0 <= through })
    }
    

    示例应该与上面的 Swift 3.0 相同。

    【讨论】:

    • 这让我想起了stackoverflow.com/a/40070339/1187415 中建议的辅助函数,它可以很普遍地使用。使用它,您可以使用for f in sequence(first: (0, 1), while: { $1 &lt;= 50 }, next: { ($1, $0 + $1)}) { print(f.1) } 打印斐波那契数。
    • @MartinR 确实不错!我之前已经对链接的答案进行了投票,但是如果您有时间并对此感到满意,那么基于该帮助者的答案将是该线程的一个很好的补充:)
    • 请原谅我这样 ping 你,但我认为你对 Swift 中的算法性能感兴趣,我想邀请你看看codereview.stackexchange.com/q/158798/35991 和 @987654327 @!
    • @MartinR 不用担心,我很高兴 ping,谢谢(将借此机会将我的 Knuth 收藏品下架)。本周某个晚上会去看看,看看我能否提出一些建设性的建议。顺便说一句,由于您还要求 Swiftyness/语义/等,您可能还想 ping Hamish(如果您还没有的话),我认为他会对这个主题感兴趣,并且渴望可能提供帮助。
    【解决方案2】:

    Swift 3.0 的替代方法是使用辅助函数

    public func sequence<T>(first: T, while condition: @escaping (T)-> Bool, next: @escaping (T) -> T) -> UnfoldSequence<T, T> {
        let nextState = { (state: inout T) -> T? in
            // Return `nil` if condition is no longer satisfied:
            guard condition(state) else { return nil }
            // Update current value _after_ returning from this call:
            defer { state = next(state) }
            // Return current value:
            return state
        }
        return sequence(state: first, next: nextState)
    }
    

    来自Express for loops in swift with dynamic range

    for f in sequence(first: (0, 1), while: { $1 <= 50 }, next: { ($1, $0 + $1)}) {
        print(f.1)
    }
    // 1 1 2 3 5 8 13 21 34
    

    请注意,为了在结果序列中包含零,它 将初始值(0, 1)替换为(1, 0)即可:

    for f in sequence(first: (1, 0), while: { $1 <= 50 }, next: { ($1, $0 + $1)}) {
        print(f.1)
    }
    // 0 1 1 2 3 5 8 13 21 34
    

    这使得“人工”检查

    if pair.1 == 0 { pair.1 = 1; return 0 }
    

    冗余。根本原因是斐波那契数可以 推广到负指数(https://en.wikipedia.org/wiki/Generalizations_of_Fibonacci_numbers):

     ... -8, 5, -3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8, ...
    

    【讨论】:

    • 很好,以前不知道负索引部分!
    • 其实好像整个helper sequence都可以降格成return sequence(state: first, next: { (condition($0) ? $0 : Optional&lt;T&gt;.none, $0 = next($0)).0 })
    • @dfri:不,即使条件不满足,即“一次太频繁”,也会计算next($0)
    • 啊,我的错,defer(原版)不会被执行(到达)以防guard 中断并返回nil
    • @dfri:没错。如果defer 语句没有被执行,那么它的代码块根本就没有被调度执行。
    【解决方案3】:

    在 Swift 3.1 中,这是一个永远生成斐波那契数的迭代器,以及从它派生的无限序列:

    class FibIterator : IteratorProtocol {
        var (a, b) = (0, 1)
    
        func next() -> Int? {
            (a, b) = (b, a + b)
            return a
        }
    }
    
    let fibs = AnySequence{FibIterator()}
    

    要打印前 10 个斐波那契数:

    > print(Array(fibs.prefix(10)))
    [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
    

    如果您想过滤或映射这个无限序列,您需要首先调用.lazy,否则过滤器或映射将严格执行并且不会终止。以下是前 5 个偶数斐波那契数:

    > print( Array(fibs.lazy.filter{$0 % 2 == 0}.prefix(5)) )
    [2, 8, 34, 144, 610]
    

    【讨论】:

      【解决方案4】:

      使用递归是不好的!递归是邪恶的!

      我宁愿这样做:

      func fibo(_ n:Int) -> Int {
      
          var a = 0
          var b = 1
      
          for _ in 0..<n {
              a += b
              b = a - b
          }
      
          return a
      }
      

      这样更快更干净!

      【讨论】:

      【解决方案5】:

      详情

      Xcode 9.3.1、Swift 4.1

      解决方案

      extension Array where Element: BinaryInteger {
      
          private mutating func fibonacci(index: Int) {
              if index >= count {
                  return
              }
              self[index] = self[index-1] + self[index-2]
              return fibonacci(index: index+1)
          }
      
          init(fibonacci count: Int) {
              self = [Element]()
              if count < 0 {
                  self = [Element]()
              }
              self = [Element](repeating: 1, count: count)
              fibonacci(index: 2)
          }
      
          static func calculate(fibonacciAt index: Int) -> Element? {
      
              if index < 0 {
                  return nil
              }
      
              if index < 2 {
                  return 1
              }
      
              func calc(a: Element, b: Element, index: Int) -> Element {
                  if index == 1 {
                      return b
                  }
                  return calc(a: b, b: a+b, index: index-1)
              }
      
              return calc(a: 1, b: 1, index: index)
          }
      }
      

      用法

      let fibonacciSequence = [Int](fibonacci: 15)
      let index = 12
      print(fibonacciSequence)
      print(fibonacciSequence[index])
      let value = [Int].calculate(fibonacciAt: index)
      print("\(value!)")
      

      结果

      【讨论】:

        【解决方案6】:

        详情

        XCode 版本 10.0 beta 6,Swift 4.2

        需要控制流来获取从 0 开始的斐波那契序列的第一次或前两次迭代。

        时间复杂度:O(n)
        空间复杂度:O(n)

        代码

        func fib(_ n: Int) -> [Int] {
        
         var fibs: [Int] = [0, 1]
         switch n
         {
         case 1:  return [fibs[0]]
         case 2:  return [fibs[0],fibs[1]]
         default:
        
         (2...n-1).forEach
         { i in
             fibs.append(fibs[i - 1] + fibs[i - 2])
         }
        
         return fibs
         }
        }
        

        用法

        fib(8)

        //打印(fib(8))

        【讨论】:

          【解决方案7】:

          摘自 David kopec 的《Swift 中的经典计算机科学问题》一书:

          递归

           var fibMemo: [UInt: UInt] = [0: 0, 1: 1] // our old base cases
          
           func fib3(n:  UInt) ­> UInt
           {
          
              if let result = fibMemo[n] 
              { 
                 // our new base case
          
                 return result
              } 
              else 
              {
          
                 fibMemo[n] = fib3(n: n ­ 1) + fib3(n: n ­ 2) // memoization
              }
          
             return fibMemo[n]!
           }
          

          通过迭代方法

          func fib4(n: UInt) ­> UInt
          {
          
               if (n == 0) 
               {
                  // special case
          
                  return n
               }
          
               var last: UInt = 0, next: UInt = 1 // initially set to fib(0) & fib(1          
          
               for _ in 1..<n {
          
                    (last, next) = (next, last + next) }
          
               return next
          }
          

          【讨论】:

            【解决方案8】:
            func fibonaci(n: Int)
            {
                var fiboNumberOne = 1
                var fiboNumberTwo = 0
            
                for i in 0..<n
                {
                    let temp = fiboNumberOne + fiboNumberTwo
                    fiboNumberOne = fiboNumberTwo
                    fiboNumberTwo = temp
                    print("Fibonaci \(fiboNumberTwo)")
            
                }
            }
            
             fibonaci(n: 5)
            

            【讨论】:

              【解决方案9】:

              如果您不需要准确性,则有 O(1) 函数可以满足您的需求:

              func fibonacci(iteration: Int) -> Int {
                return Int(round(pow(1.618033988749895, Double(iteration)) / 2.23606797749979))
              }
              

              那么它是如何工作的:

              print((0..<40).map(fibonacci))
              // prints [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
              

              在 70 次迭代之前完美运行。

              警告:第 71 次迭代返回 308061521170130 而不是 308061521170129

              【讨论】:

                【解决方案10】:

                我刚刚看到 Dhaval Gevariya 代码,只是将 print fibonacci 移到上面而不是下面,现在它也会打印 0

                func fibonaci(n: Int)
                {
                    var fiboNumberOne = 1
                    var fiboNumberTwo = 0
                
                    for i in 0..<n
                    {
                        print("Fibonaci \(fiboNumberTwo)")
                        let temp = fiboNumberOne + fiboNumberTwo
                        fiboNumberOne = fiboNumberTwo
                        fiboNumberTwo = temp
                        
                    }
                }
                
                 fibonaci(n: 5)
                

                【讨论】:

                  【解决方案11】:

                  // MARK: - 功能

                   func fibonacciSeries(_ num1 : Int,_ num2 : Int,_ term : Int,_ termCount : Int) -> Void{
                          if termCount != term{
                              print(num1)
                              fibonacciSeries(num2, num2+num1, term, termCount + 1)
                          }
                      }
                      
                  

                  // MARK: - 函数调用 斐波那契数列(0, 1, 5, 0)

                  // MARK: - 输出 0 1 1 2 3

                  注意只需更改斐波那契数列的 No Of 项。

                  【讨论】:

                    猜你喜欢
                    • 2011-04-26
                    • 2011-12-18
                    • 2015-04-25
                    • 1970-01-01
                    • 2010-11-09
                    • 1970-01-01
                    • 1970-01-01
                    • 2020-01-18
                    • 2017-11-13
                    相关资源
                    最近更新 更多