Ruby lazy枚举器根据使用

时间:2016-04-26 22:04:49

标签: ruby functional-programming lazy-evaluation

我试图用ruby以功能方式解决Project Euler #58

简单地说,我创建了一个枚举器来返回每个环的转角数。然后,我在枚举器上链接功能操作符。当我得到我的结果时,我发现根据我的使用方式,它有不同的类。

spiral = Enumerator.new do |yielder|
    n = 3
    step = 2
    loop do
        vals = n.step(nil, step).take(4)
        yielder.yield vals
        step += 2
        n = vals.last + step
    end
end

primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113]

levels = spiral
    .lazy
    .map { |ring| ring.count { |n| primes.include? n } }    
    .with_object({:total=>1.0, :primes=>0})
    .take_while do |ring_primes, counts|
        counts[:total] += 4
        counts[:primes] += ring_primes
        (counts[:primes] / counts[:total]) > 0.5
    end

levels的类是一个懒惰的枚举器。我希望它包含每个环中的素数[3,2,3等] - 请参阅项目euler参考。

如果我只是从枚举器打印出来,我得到了我期望的结果:

levels.each do |level|
    puts "#{level}"
end

返回:

3
2
3
1

但是如果我循环.with_index我得到一个数组结果,其中期望值是第一个成员,第二个是我的.with_object参数

levels.each.with_index do |level, ix|
    puts "#{ix}: #{level}"
end

返回:

0: [3, {:total=>5.0, :primes=>3}]
1: [2, {:total=>9.0, :primes=>5}]
2: [3, {:total=>13.0, :primes=>8}]
3: [1, {:total=>17.0, :primes=>9}]

为什么懒惰的枚举器会以这种方式工作?我将来如何预测呢?

更新

我询问了IRC红宝石频道,没有人对它有任何想法。他们说他们在一两天前讨论过这个问题并没有得出任何结论。

一般来说,似乎必须处理它并继续前进。

1 个答案:

答案 0 :(得分:2)

这里发生的事情是你方便地忽略了返回的结构并拔出要显示的第一个项目。在这种情况下,第一项是您生成的counts结构。

看看这个:

levels.each do |*level|
  puts level.inspect
end

这会向您显示levels结果中的实际内容。当Ruby调用lambda时,它将丢弃任何与块接受的参数数量不相符的附加数据。

如果您不需要该元数据,请将其删除:

levels = spiral
  .lazy
  .map { |ring| ring.count { |n| primes.include? n } }    
  .with_object({:total=>1.0, :primes=>0})
  .take_while do |ring_primes, counts|
    counts[:total] += 4
    counts[:primes] += ring_primes
    (counts[:primes] / counts[:total]) > 0.5
  end
  .map { |r,_| r }

删除结果中的无关元素。

这是一种清理你的枚举器的方法:

class Spiral
  include Enumerable

  def each
    Enumerator.new do |yielder|
      n = 3
      step = 2
      loop do
        vals = n.step(nil, step).take(4)
        yielder.yield vals
        step += 2
        n = vals.last + step
      end
    end
  end
end

然后你可以创建一个:

Spiral.new.each ...