Когда полезен метод ввода 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