开发者

When is the Enumerator::Yielder#yield method useful?

The question "Meaning of the word yield" ment开发者_Python百科ions the Enumerator::Yielder#yield method. I haven't used it before, and wonder under what circumstances it would be useful.

Is it mainly useful when you want to create an infinite list of items, such as the Sieve of Eratosthenes, and when you need to use an external iterator?


"How to create an infinite enumerable of Times?" talks about constructing and lazy iterators, but my favorite usage is wrapping an existing Enumerable with additional functionality (any enumerable, without needing to know what it really is, whether it's infinite or not etc).

A trivial example would be implementing the each_with_index method (or, more generally, with_index method):

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

Extending to something not already implemented in the core library, such as cyclically assigning value from a given array to each enumerable element (say, for coloring table rows):

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

The whole point is that these methods return an Enumerator, which you then combine with the usual Enumerable methods, such as select, map, inject etc.


For example you can use it to construct Rack response bodies inline, without creating classes. An Enumerator can also work "outside-in" - you call Enumerator#each which calls next on the enumerator and returns every value in sequence. For example, you can make a Rack response body returning a sequence of numbers:

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


Since Mladen mentioned getting other answers, I thought I would give an example of something I just did earlier today while writing an application that will receive data from multiple physical devices, analyze the data, and connect related data (that we see from multiple devices). This is a long-running application, and if I never threw away data (say, at least a day old with no updates), then it would grow infinitely large.

In the past, I would have done something like this:

delete_old_stuff if rand(300) == 0

and accomplish this using random numbers. However, this is not purely deterministic. I know that it will run approximately once every 300 evaluations (i.e. seconds), but it won't be exactly once every 300 times.

What I wrote up earlier looks like this:

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

and I can replace delete_old_stuff if rand(300) == 0 with counter.next

Now, I'm sure there is a more efficient or pre-made way of doing this, but being sparked to play with Enumerator::Yielder#yield by your question and the linked question, this is what I came up with.


It seems to be useful when you have multiple objects you want to enumerate over, but flat_map isn't suitable, and you want to chain the enumeration with another action:

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
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜