Enumerator :: Yielder#yield方法何时有用?

时间:2011-02-21 22:06:51

标签: ruby yield enumerable

This question提到Enumerator::Yielder#yield方法。我之前没有使用它,我想知道它在什么情况下会有用。

当您想要创建无限的项目列表(例如Eratosthenes的Sieve)以及何时需要使用外部迭代器时,它是否主要有用?

4 个答案:

答案 0 :(得分:10)

虽然构建infinite lists和延迟迭代器非常方便,但我最喜欢的用法是将现有的Enumerable包含其他功能(任何可枚举的,无需知道它是什么,无论是否无限等)。

琐碎的例子是实施each_with_index方法(或者更常见的是with_index方法):

module Enumerable
  def my_with_index
    Enumerator.new do |yielder|
      i = 0
      self.each do |e|
        yielder.yield e, i
        i += 1
      end
    end
  end

  def my_each_with_index
    self.my_with_index.each do |e, i|
      yield e, i
    end
  end
end

[:foo, :bar, :baz].my_each_with_index do |e,i|
  puts "#{i}: #{e}"
end
#=>0: foo
#=>1: bar
#=>2: baz

现在让我们扩展到corelib :)中尚未实现的内容,例如将给定数组中的值循环分配给每个可枚举元素(例如,用于着色表行):

module Enumerable
  def with_cycle values
    Enumerator.new do |yielder|
      self.each do |e|
        v = values.shift
        yielder.yield e, v
        values.push v
      end
    end
  end
end

p (1..10).with_cycle([:red, :green, :blue]).to_a # works with any Enumerable, such as Range
#=>[[1, :red], [2, :green], [3, :blue], [4, :red], [5, :green], [6, :blue], [7, :red], [8, :green], [9, :blue], [10, :red]]

重点是这些方法返回Enumerator,然后将其与常用的可枚举方法结合使用,例如selectmapinject等。

答案 1 :(得分:1)

例如,您可以使用它来内联构建Rack响应主体,而无需创建类。 Enumerator也可以“从外到内”工作 - 您调用Enumerator#each调用枚举器上的next并按顺序返回每个值。例如,您可以使Rack响应主体返回一系列数字:

run ->(env) {
  body = Enumerator.new do |y|
   9.times { |i| y.yield(i.to_s) }
  end
  [200, {'Content-Length' => '9'}, body]
}

答案 2 :(得分:0)

由于Mladen提到了其他答案,我想我会举一个我今天早些时候做的事情的例子。我正在编写一个应用程序,它将从多个物理设备接收数据,分析数据并连接相关数据(我们从多个设备中看到)。这是一个长期运行的应用程序,如果我从不丢弃数据(例如,至少一天没有更新),那么它将变得无限大。

我只学习了大约7-8个月的Ruby,所以在过去,我会做这样的事情:

delete_old_stuff if rand(300) == 0

使用随机数完成此操作。但是,这不是纯粹的确定性。我知道它每300次评估大约会运行一次(即几秒钟),但不会每300次运行一次。

我之前写的内容如下:

counter = Enumerator.new do |y|
  a = (0..300)
  loop do
    a.each do |b|
      y.yield b
    end
    delete_old_stuff
  end
end

我可以用delete_old_stuff if rand(300) == 0

替换counter.next

现在,我确信有一个更高效或者预先制定的方式,但是通过你的问题和相关问题引发Enumerator::Yielder#yield,这就是我想出来了。

(显然,如果你看到改善这个或任何事情的方法,请告诉我!我想尽可能多地学习)

答案 3 :(得分:0)

当您想要枚举多个对象时,它似乎很有用,但flat_map不合适,并且您希望将枚举与另一个动作链接起来:

module Enumerable
  def count_by
    items_grouped_by_criteria = group_by {|object| yield object}
    counts = items_grouped_by_criteria.map{|key, array| [key, array.length]}
    Hash[counts]
  end
end

def calculate_letter_frequencies
  each_letter.count_by {|letter| letter}
end

def each_letter
  filenames = ["doc/Quickstart", "doc/Coding style"]
  # Joining the text of each file into a single string would be memory-intensive
  enumerator = Enumerator.new do |yielder|
    filenames.each do |filename|
      text = File.read(filename)
      text.chars.each {|letter| yielder.yield(letter)}
    end
  end
  enumerator
end

calculate_letter_frequencies