Как использовать счетчик

В документации Ruby Array Class я часто нахожу:

Если блок не задан, вместо него возвращается перечислитель.

Почему бы мне не передать блок #map? Какая польза от того, что я делаю просто:

[1,2,3,4].map

вместо того, чтобы делать:

[1,2,3,4].map{ |e| e * 10 } # => [10, 20, 30, 40]

Может ли кто-нибудь показать мне очень практичный пример использования этого счетчика?


person mjnissim    schedule 05.12.2013    source источник
comment
так хорошо продумано! – Мне действительно нравится Ruby, это мой любимый язык, но это не то качество, которое я когда-либо приписывал бы Ruby :-D   -  person Jörg W Mittag    schedule 05.12.2013
comment
@JörgWMittag, я так рада, что ты заглянула. Мне очень нравятся ваши ответы на этом форуме. Я даже рассказал своей жене об этом парне из Германии. Вы написали какие-нибудь книги?   -  person mjnissim    schedule 05.12.2013
comment
@mjnissim Да, Jörg действительно один из моих любимых ответчиков... :)   -  person Arup Rakshit    schedule 05.12.2013


Ответы (6)


Основное различие между Enumerator и большинством других структур данных в основной библиотеке Ruby (Array, Hash) и стандартной библиотеке (Set, SortedSet) заключается в том, что Enumerator может быть бесконечным. У вас не может быть Array всех четных чисел, или потока нулей, или всех простых чисел, но вы определенно можете иметь такое Enumerator:

evens = Enumerator.new do |y|
  i = -2
  y << i += 2 while true
end

evens.take(10)
# => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

zeroes = [0].cycle

zeroes.take(10)
# => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Итак, что вы можете сделать с таким Enumerator? Ну, три вещи, в основном.

  1. Enumerator смешивается с Enumerable. Таким образом, вы можете использовать все методы Enumerable, такие как map, inject, all?, any?, none?, select, reject и так далее. Просто имейте в виду, что Enumerator может быть бесконечным, тогда как map возвращает Array, поэтому попытка map бесконечного Enumerator может создать бесконечно большое Array и занять бесконечное количество времени.

  2. Существуют методы упаковки, которые каким-то образом «обогащают» Enumerator и возвращают новый Enumerator. Например, Enumerator#with_index добавляет в блок «счетчик циклов», а Enumerator#with_object добавляет мемо-объект.

  3. Вы можете использовать Enumerator так же, как вы использовали бы его в других языках для внешней итерации, используя метод Enumerator#next, который даст вам либо следующее значение (и переместит Enumerator вперед), либо raise исключение StopIteration, если Enumerator конечно и у вас есть дошел до конца.

Например, бесконечный диапазон: (1..1.0/0)

person Jörg W Mittag    schedule 05.12.2013
comment
Йорг, пожалуйста, подумайте о написании книги. Я уверен, что есть темы, которые еще не освещены должным образом. Подобно Дэвиду Блэку и Рассу Олсену, у вас есть дар объяснять, который в сочетании с глубоким пониманием материала может быть очень полезен другим. - person mjnissim; 05.12.2013

Хороший вопрос.

Что, если мы хотим сделать несколько вещей с созданным перечислителем? Мы не хотим обрабатывать его сейчас, потому что это означает, что нам может понадобиться создать еще один позже?

my_enum = %w[now is the time for all good elves to get to work].map # => #<Enumerator: ["now", "is", "the", "time", "for", "all", "good", "elves", "to", "get", "to", "work"]:map>

my_enum.each(&:upcase) # => ["NOW", "IS", "THE", "TIME", "FOR", "ALL", "GOOD", "ELVES", "TO", "GET", "TO", "WORK"]
my_enum.each(&:capitalize) # => ["Now", "Is", "The", "Time", "For", "All", "Good", "Elves", "To", "Get", "To", "Work"]
person vgoff    schedule 05.12.2013
comment
vgoff, очень хороший ответ. Но вы сказали, что мы не хотим обрабатывать его сейчас, потому что это означает, что нам может понадобиться создать еще один позже. Я не понимаю, почему это не обрабатывается сейчас, и почему бы просто не использовать массив. - person mjnissim; 05.12.2013
comment
Если мы просто воспользуемся enumerable прямо в этот момент, мне придется повторить мою команду .map. Таким образом, я выполняю команду карты один раз, и я могу использовать Enumerator больше, чем один раз. Это преимущество возможности вернуть Enumerator. Мы можем использовать этот экземпляр Enumerator снова и снова. Нам не нужно дублировать эту работу. - person vgoff; 05.12.2013
comment
Так это более эффективно с точки зрения машины? Работает ли Ruby меньше, когда я дважды перебираю перечислитель, а не дважды массив? или я работаю меньше? или оба? - person mjnissim; 05.12.2013
comment
Если вы создаете что-то один раз, это делается. Если вы создаете одно и то же более одного раза, то это во много раз больше работы. - person vgoff; 05.12.2013

Функция возврата enumerable, когда блок не задан, в основном используется при объединении функций из перечислимого класса вместе. Как это:

abc = %w[a b c]
p abc.map.with_index{|item, index| [item, index]} #=> [["a", 0], ["b", 1], ["c", 2]]

редактировать:

Я думаю, что мое собственное понимание этого поведения слишком ограничено, чтобы дать правильное понимание внутренней работы Ruby. Я думаю, самое важное, что следует отметить, это то, что аргументы передаются так же, как и для процедур. Таким образом, если массив передан, он будет автоматически "размазан" (какое-нибудь лучшее слово для этого?). Я думаю, что лучший способ понять это — просто использовать несколько простых функций, возвращающих перечисляемые числа, и начать экспериментировать.

abc = %w[a b c d]
p abc.each_slice(2)                #<Enumerator: ["a", "b", "c", "d"]:each_slice(2)>
p abc.each_slice(2).to_a           #=> [["a", "b"], ["c", "d"]]
p abc.each_slice(2).map{|x| x}     #=> [["a", "b"], ["c", "d"]]
p abc.each_slice(2).map{|x,y| x+y} #=> ["ab", "cd"]
p abc.each_slice(2).map{|x,| x}    #=> ["a", "c"] # rest of arguments discarded because of comma.
p abc.each_slice(2).map.with_index{|array, index| [array, index]} #=> [[["a", "b"], 0], [["c", "d"], 1]]
p abc.each_slice(2).map.with_index{|(x,y), index| [x,y, index]}   #=> [["a", "b", 0], ["c", "d", 1]]
person hirolau    schedule 05.12.2013
comment
Хиролау, это очень интересный ответ. Я нахожу уровень абстракции немного трудным для понимания на данный момент. Я буду рад, если вы объясните немного больше, но даже если вы этого не сделаете, по крайней мере, я узнал, что вы можете связывать методы таким образом, и я попытаюсь немного разжевать это. - person mjnissim; 05.12.2013

В дополнение к ответу hirolau есть еще один метод lazy, который вы можете комбинировать для изменения перечислителя.

a_very_long_array.map.lazy

Если бы map всегда принимал блок, то должен был бы быть другой метод, такой как map_lazy, и аналогично для других итераторов, делающих то же самое.

person sawa    schedule 05.12.2013

Я предполагаю, что иногда вы хотите передать Enumerator другому методу, несмотря на то, откуда взялся этот Enumerator: map, slice, что угодно:

def report enum
  if Enumerator === enum
    enum.each { |e| puts "Element: #{e}" }
  else
    raise "report method requires enumerator as parameter"
  end
end

> report %w[one two three].map
# Element: one
# Element: two
# Element: three

> report (1..10).each_slice(2)    
# Element: [1, 2]
# Element: [3, 4]
# Element: [5, 6]
# Element: [7, 8]
# Element: [9, 10]
person Aleksei Matiushkin    schedule 05.12.2013
comment
Спасибо братан. Но опять же, почему перечислитель, а не просто массив? - person mjnissim; 05.12.2013
comment
Просто потому, что вы можете реализовать свой собственный Enumerator. И потому что есть помощники Kernel#to_enum и Kernel#enum_for. Enumerator более общий, чем Array: взгляните на примеры здесь. - person Aleksei Matiushkin; 05.12.2013

Enumerator Класс, допускающий как внутреннюю, так и внешнюю итерацию.

=> array = [1,2,3,4,5]
=> array.map
=> #<Enumerator: [2, 4, 6, 8, 10]:map>
=> array.map.next
=> 2
=> array.map.next_values
=> [0] 2
person Зелёный    schedule 05.12.2013
comment
Достойный пример, но тогда вам также нужно знать, что вы ожидаете возникновения исключения, если вы превысите границы перечислителя. - person vgoff; 05.12.2013
comment
это просто простой пример, я не использую эту технику - person Зелёный; 05.12.2013
comment
Спасибо, Monk_Code, но я понял, что вы, вероятно, захотите, чтобы я сделал map = array.map, а затем использовал map.next, верно? - person mjnissim; 05.12.2013
comment
в этом примере перечислитель (array.map) похож на итератор Freezy, вы можете проходить каждый шаг без блока. - person Зелёный; 05.12.2013