【问题标题】:Fibonacci One-Liner斐波那契单线
【发布时间】:2011-09-19 02:21:20
【问题描述】:

我正在尝试在 Ruby 单行代码中解决来自 Project Euler 的问题,我很好奇 question two 是否有更优雅的解决方案:

斐波那契数列中的每个新项都是通过添加前两项来生成的。从 1 和 2 开始,前 10 个术语将是:

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

通过考虑斐波那契数列中值不超过四百万的项,求偶数项之和。

这是我在 Ruby 中的单行解决方案:

(1..32).inject([0,1]) {|arr, i| (arr << arr[-1] + arr[-2] if arr[-1] + arr[-2] <= 4000000) || arr}.inject(0) {|total, i| total += i.even? ? i : 0}

我在这里主要担心的是我使用范围 (1..32) 只是因为我碰巧知道在斐波那契数列中的数字开始超过 4,000,000 之前,这就是所有必要的。我希望以某种方式将其内置到单行中,但我无法弄清楚。

不允许使用分号!

【问题讨论】:

  • 我认为,如果“单线”解决方案包含多个块,这会有点颠覆您的挑战精神。我的意思是,如果您不介意一行 500 个字符长且完全不可读的话,您可以用同样的方式编写 Java 单行。
  • 这与 Ruby 无关,这只是我正在学习的语言。这只是为了好玩。
  • @aroth,在 Ruby 中链接块就像使用多个算术运算符进行赋值一样自然。对于一个更能打破规则精神的单行字,请参阅我的解决方案:分号是一个死的赠品。
  • @Wayne - 如果在 Ruby 中链接块总是通过使用一行代码来完成,那么我只能说是啊......我永远不会理解为什么看似理性的人会采取不必要的做法混淆代码并使其“自然”。 Ruby 作为一门语言背后的部分设计理念是它应该易于人类阅读和理解,在您的两个示例解决方案中,多行的解决方案是迄今为止最具可读性的。
  • @aroth,我同意。除非它更具可读性,否则我不会将块链接在一行上。有时是,通常不是。我的例子中的单行是因为 OP 要求它,而不是因为它是我要写的。也就是说,写单行一种有效的练习,就像音乐家演奏音阶一样。您不会在生产代码中编写一个衬里,也不会在音乐会中演奏音阶。

标签: ruby fibonacci


【解决方案1】:

我最喜欢的解决方案是使用 Hash,其值可以由匿名函数确定:

fibonacci = Hash.new{ |h,k| h[k] = k < 2 ? k : h[k-1] + h[k-2] }

fibonacci[6]  # => 8 
fibonacci[50] # => 12586269025

这是一个“真正的”单线,非常 Ruby 风格。

【讨论】:

  • 非常聪明!但是您使用的是冒号 - 这不是使用分号的两倍吗?
  • 太棒了!您可以通过使用哈希来驱动函数来获得自动记忆。这就是优雅!
  • fibonacci[2299] # =&gt; stack level too deep (SystemStackError)
  • 我想强调两种方法之间的性能差异。哈希:codefibonacci = Hash.new{ |h,k| h[k] = k code Lambda: code fibo = lambda { |x| (xcode
  • 不带冒号:fib = Hash.new { |fib, n| fib[n] = fib[n - 1] + fib[n - 2] }.merge!(0 =&gt; 0, 1 =&gt; 1)
【解决方案2】:

使用 Ruby 1.9 枚举器:

fib = Enumerator.new do |yielder|
  i = 0
  j = 1
  loop do
    i, j = j, i + j
    yielder.yield i
  end
end

p fib.take_while { |n| n <= 4E6 }
# => [1, 1, 2 ... 1346269, 2178309, 3524578]

一行:

p Enumerator.new { |yielder| i, j = 0, 1; loop {i, j = j, i + j; yielder.yield i} }.take_while { |n| n <= 4E6}

【讨论】:

  • 不久前我发现了i=j+j=i这个奇怪的结构。和i, j = j, i + j是一样的。
  • @Jonas,你是说j = i + i = j吗?这是一个有趣的结构!我不想在生产代码中使用它,但它是思考 Ruby 工作原理的好方法。感谢您指出这一点。
  • 我也不会在生产代码中使用它,而我将它们混淆是原因的一个很好的指标。
【解决方案3】:

受 Alex 的回答启发:

# Ruby 1.8.7
f = lambda { |x| x < 2 ? x : f.call(x-1) + f.call(x-2) }
puts f.call(6)   #=> 8

# Ruby 1.9.2
f = ->(x){ x < 2 ? x : f[x-1] + f[x-2] }
puts f[6]        #=> 8

【讨论】:

  • fib = -&gt;(n, i=0, j=1){(1..n).map{i=j+j=i}} 调用它fib[7]
  • 我错过了答案应该是第 n 个斐波那契数,而不是序列本身。这是fib = -&gt;(n, i=0, j=1){n.times{i=j+j=i};i} 的另一个愚蠢的 lambda 因为i=j+j=i 是一个如此古怪的结构,我认为它不值得作为答案。
  • 这是一种利用运算符优先顺序的攻击性方式。试试这个是irb。 i=0;j=1 然后运行 ​​i=j+j=i; puts "i:#{i}, j:#{j}" 几次。
  • i=j+j=i 从左到右进行评估。起点i=0;j=1。第一个 i 将设置为 1 + 0,因为 = 在 + 之前。现在i=1;j=0i 将设置为 0 + 1。我们到达 i=1;j=1i 将设置为 1 + 1。i=2;j=1i 将设置为 1 + 2。i=3;j=2i 将设置为 2 + 3。i=5;j=3 等等,从而产生斐波那契数列。
  • @MikeH-R ...而;i; return i 的同义词。
【解决方案4】:

我最喜欢的是:

def fib(n)
  (0..n).inject([1,0]) { |(a,b), _| [b, a+b] }[0]
end

来自https://gist.github.com/1007228

【讨论】:

    【解决方案5】:

    这个怎么样?

    (((1 + 5 ** 0.5) / 2) ** 35 / 5 ** 0.5 - 0.5).to_i / 2
    

    See this answer 解释一下。)

    【讨论】:

    • 我喜欢这个解决方案,但你不知道你为什么使用 35 的意义吗?鉴于欧拉项目的问题,你真的只知道数字 4000000 并将偶数相加。
    • @pjammer: 35 可以替换为(Math.log(4000000 * 5 ** 0.5, (1 + 5 ** 0.5) / 2) + 2).to_i,如果你愿意的话。
    【解决方案6】:

    这是一个 ruby​​ 2.0 解决方案,没有使用不是惰性的注入/减少:

    (1..Float::INFINITY).
      lazy.
      with_object([0,1]).
      map { |x, last| last[1] = last[0] + (last[0] = last[1]) }.
      select { |x| x % 2 == 0 }.
      take_while { |x| x < 4_000_000 }.
      reduce(&:+)
    

    我不是特别喜欢斐波那契生成器,因为它不包括初始 0。这个解决方案还利用了第一个奇数 F3 (F1 在这个序列生成器中)。

    更清洁(斐波那契)和正确(在 Liber Abaci 的定义中)的解决方案是:

    (1..Float::INFINITY).
      lazy.
      with_object([0,1]).
      map { |x, last| last[1] = last[0] + (last[0] = last[1]);last[0] }.
      select { |x| x % 2 == 0 }.
      take_while { |x| x < 4_000_000 }.
      reduce(&:+)
    

    这个解决方案包括一个分号,但我不知道这样使用时是否算数:)。

    [更新]

    这是一个正确的 Fibonacci 生成器(从 0 开始)解决方案,没有分号(顺便说一句,这是 javascript 分号大战吗?!?):)

    (1..Float::INFINITY).
      lazy.
      with_object([0,1]).
      map { |x, last| last[0].tap { last[1] = last[0] + (last[0] = last[1]) } }.
      select { |x| x % 2 == 0 }.
      take_while { |x| x < 4_000_000 }.
      reduce(&:+)
    

    【讨论】:

      【解决方案7】:

      基于 Alex 的 Hash,这可能会让你失明,但它是一行,没有分号,并且消除了范围依赖性。 instance_eval 技巧对 oneliners 和高尔夫非常有用,尽管它是可怕的 Ruby。

      Hash.new{|h,k|h[k]=k<2?k:h[k-1]+h[k-2]}.update(sum: 0,1=>1).instance_eval {self[:sum]+= self[keys.last+1].even? ? self[keys.last] : 0 while values.last < 4E6 || puts(fetch :sum)}
      

      输出:4613732

      我警告过你这太可怕了。如果不使用分号,我无法让它真正返回值,抱歉。

      【讨论】:

        【解决方案8】:

        我意识到这是一个古老的问题,已被归类为已回答,但没有人设法在一个块中解决这个问题,他们实际上都没有在一行和一个块中给出偶数项的总和,并且没有分号(只是注意到韦恩斯确实用一行来解决,但我认为一个块解决方案可能会很好地响应 aroth)。这是一个解决方案:

        (1..Float::INFINITY).inject([0,1,0]){|a| if a[0]+a[1] < 4000000 then [a[1],a[0]+a[1],(a[0]+a[1]).even? ? a[2] + (a[0]+a[1]) : a[2]] else break a[2] end }
        

        对于一个带有一个分号的稍微清晰的版本。

        (1..Float::INFINITY).inject([0,1,0]){|a| sum=a[0]+a[1]; if sum < 4000000 then [a[1],sum,sum.even? ? a[2] + sum : a[2]] else break a[2] end }
        

        我想我也会解释一下,数组中的三条信息(如a 在每次迭代中)第一个斐波那契数、第二个斐波那契数和偶数项之和。考虑到这一点,我认为这段代码非常清晰。

        需要注意的是,这和clems基本一样,只是在一个街区里

        【讨论】:

        • 而且 afaik 它似乎很有效,并且可以记住信息而不存储太多信息(因此速度和内存效率都很高)
        • +1 指出其余部分遗漏的内容:我们非常专注于谎言。我们忘记添加 OP 要求的最后一点的生成器。
        【解决方案9】:
        puts (1..20).inject([0, 1]){|Fibonacci| Fibonacci << Fibonacci.last(2).inject(:+) }
        

        这是我使用注入关键字打印斐波那契数列的最佳解决方案。 解释: 1) .inject([0,1]) 将保存系列的集合 (1) 元素的默认值 (0) 第一个值。 2) 首先斐波那契对象将有 0, 1 使用 Fibonacci.last(2) 将通过注入 3) .inject(:+) 将添加 0+1 4) 这将添加 0+1 = 1,然后将被推送到 Fibonacci,在下一次迭代中,外部 inject([0,1]) 将变为 inject(1,2) 这里1是sum(0+1)之后的值,2是collection的下一次迭代值。 以此类推,直到收集结束

        所以这个系列会是这样的

        0
        1
        1
        2
        3
        5
        8
        13
        21
        34
        55
        89
        144
        233
        377
        610
        987
        1597
        2584
        4181
        6765
        10946
        

        【讨论】:

          【解决方案10】:

          返回最大为Fib(70) 的正确值,除此之外只是一个近似值。但速度极快:

          (((Math.sqrt(5.0) + 1.0) / 2.0)**n / Math.sqrt(5.0) + 0.5).floor
          

          (见https://en.wikipedia.org/wiki/Fibonacci_number#Computation_by_rounding解释)

          【讨论】:

            【解决方案11】:

            使用 ruby​​ 2.0 中的新lazy,你可以这样写。

            puts (1..Float::INFINITY).lazy.map{|n| (0..n).inject([1,0]) {|(a,b), _| [b, a+b]}[0] }.take_while{|n| n < 4000000}.select{|x| x % 2 == 0}.reduce(:+)
            

            【讨论】:

              【解决方案12】:

              作为上述答案的总结性解决方案,并附上我的拙劣补充:

              32.
                times.
                lazy.
                with_object([0, 1]).map { |_, fib| fib[1] = fib[0] + fib[0] = fib[1]; fib[0] }.
                take_while(&:>.to_proc.curry(2)[4*10**6]).
                select(&:even?).
                inject(:+)
              

              我不太喜欢 currying 的外观,但不希望它看起来与其他答案相似。替代take_while 仅适用于这种情况:

                take_while { |value| value < 4*10**6 }.
              

              【讨论】:

                【解决方案13】:

                我现在可以想到 4 种方法来实现斐波那契目标!

                1. 使用 stabby lambda:
                puts 'Fibonacci Sequence in a Line: ', ->(a=1, b=0) { 10.times.collect { (a, b = b, a + b)[0] } }.call
                

                这将评估 10 个系列。但是如果你想获取用户的号码:

                puts 'Fibonacci Sequence in a Line: ', ->(a=1, b=0) { gets.to_i.times.collect { (a, b = b, a + b)[0] } }.call
                
                1. 使用tap 方法:
                [0, 1].tap { |a| 10.times { a.push(a[-1] + a[-2]) } }
                
                1. 使用reduce / inject 方法:
                (1..10).reduce([0, 1]) { |a| a.push(a.last(2).sum) }
                

                10.times.reduce([0, 1]) { |a| a.push(a.last(2).sum) }
                
                1. 使用each_with_objectmap.with_object 方法:
                10.times.each_with_object([0, 1]) { |_, a| a.push(a.last(2).sum) }
                

                注意:如果您没有 Ruby 2.4+,您可能没有 sum 方法。在这种情况下,您可以使用ary[-2] + ary[-1]ary.last(2).reduce(:+) 添加最后两个元素。

                解决您的问题:

                通过考虑斐波那契数列中值不超过四百万的项,求偶数项之和。

                [0, 1].tap { |a| until (s = a.last(2).sum) > 4_000_000 do a.push(s) end }.select(&:even?).sum
                

                或者(不是那么好):

                [0, 1].tap { |a| loop while a.push(a.last(2).sum)[-1] < 4_000_000 }.tap(&:pop).select(&:even?).sum
                

                输出: 4613732

                希望这会有所帮助!

                【讨论】:

                  【解决方案14】:

                  这是欧拉问题 #2 的单行红宝石解决方案

                  (0..4000000).take_while{|i| (0..i).reduce([1,0]){|(a,b), _| [b, a+b]}[0] <= 4000000 }.map{|i| (0..i).reduce([1,0]){|(a,b), _| [b, a+b]}[0] }.select{|i| i%2 == 0}.reduce(:+)
                  

                  还是为了更好的可读性??

                  (0..4000000) .
                  take_while {|i| (0..i).reduce([1,0]){|(a,b), _| [b, a+b]}[0] <= 4000000} .
                  map {|i| (0..i).reduce([1,0]){|(a,b), _| [b, a+b]}[0]} .
                  select {|i| i%2 == 0} .
                  reduce(:+)
                  

                  【讨论】:

                    【解决方案15】:

                    (1..32).inject([0, 1]) { |fib| fib &lt;&lt; fib.last(2).inject(:+) }

                    【讨论】:

                      【解决方案16】:

                      这是我的一个班轮,@fib 表在我们得到方法返回时被填充..

                      @fib=[0,1];def fib num; return 0 if num < 0; @fib[num]||=fib(num-1)+fib(num-2);end
                      

                      【讨论】:

                        【解决方案17】:

                        简洁优雅才是最好的方式,对吧?

                        a0 = 1; a1 = 1; 20.times {|i| b = a0 + a1; a0 = a1; a1 = b; puts b };
                        

                        输出:

                        2
                        3
                        5
                        8
                        13
                        21
                        34
                        55
                        89
                        144
                        233
                        377
                        610
                        987
                        1597
                        2584
                        4181
                        6765
                        10946
                        17711
                        => 20
                        

                        【讨论】:

                          猜你喜欢
                          • 2020-04-19
                          • 1970-01-01
                          • 2012-12-29
                          • 1970-01-01
                          • 1970-01-01
                          • 2015-06-05
                          • 2014-05-23
                          • 2013-08-09
                          • 2011-11-27
                          相关资源
                          最近更新 更多