Когда полезен метод ввода Enumerator:: Yielder #?

Этот вопрос упоминает метод Enumerator::Yielder#yield. Я не использовал его раньше, и мне интересно, при каких обстоятельствах это было бы полезно.

В основном это полезно, когда вы хотите создать бесконечный список элементов, например, "Сито эратосфенов", и когда вам нужно использовать внешний итератор?

Ответ 1

Хотя это довольно удобно для создания бесконечных списков и ленивых итераторов, мое любимое использование - это перенос существующего 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, которые затем объединяются с обычными методами Enumerable, такими как select, map, inject и т.д.

Ответ 2

Например, вы можете использовать его для создания встроенных в Rack объектов, без создания классов. Enumerator также может работать "снаружи" - вы вызываете Enumerator#each, который вызывает next в перечислении и возвращает каждое значение в последовательности. Например, вы можете сделать тело ответа стойки, возвращающее последовательность чисел:

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

Ответ 3

Поскольку Младен упомянул о получении других ответов, я подумал, что приведу пример того, что я только что сделал сегодня сегодня. Я пишу приложение, которое будет получать данные с нескольких физических устройств, анализировать данные и связывать связанные данные (которые мы видим с нескольких устройств). Это долговременное приложение, и если я никогда не отбрасываю данные (скажем, по крайней мере, один день без обновлений), тогда он будет расти бесконечно большим.

Я изучал Ruby всего 7-8 месяцев, поэтому в прошлом я бы сделал что-то вроде этого:

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

Теперь я уверен, что есть 1) более эффективный или 2) предварительно сделанный способ сделать это, но, будучи вызванным, чтобы играть с Enumerator::Yielder#yield по вашему вопросу и связанному с ним вопросу, это то, что я придумал с.

(и, очевидно, если вы видите способ улучшить это или что-то еще, дайте мне знать! Я хочу узнать как можно больше)

Ответ 4

Кажется полезным, когда у вас есть несколько объектов, которые вы хотите перечислить, но 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