Всички обичаме безброй методи:

animals = ["cat","dog","iguana"]
animals.any?{|word| word.length > 3}    #=> True

Изброимите методи избират всеки елемент от масив (или колекция) и предават този елемент на някаква процедура (като умножение или друг метод).

Нека да разгледаме „документацията“ за някои изброими методи:

map { |obj| блок } → масив

карта → изброител

Очевидно има два начина за използване на #map. Нека го използваме и по двата начина.

  1. Използвайте номер едно:
[1,5,3,48].map{|item| item * 2 }   #=> [2, 10, 6, 96]

#map използва #each, за да даде всеки елемент в блока, който сме дефинирали:

Резултатът е масив, така че можем лесно да ги комбинираме (или верижно):

[1,5,3,48].map{|i| i * 2}.select{|i| i > 3}   # => [10, 6, 96]

Този код не е красив, но работи.

2) Използвайте номер две: map → an_enumerator

Нека проверим класа на изброителя:

Array.new.map.class                   # => Enumerator
# or
[0, -1, 3, 2, 1, 3].select.class      # => Enumerator

Когато оставите блока на метод на изброител, вие създавате нов изброител, който е обект.

[1,5,3,48].map   # => #<Enumerator: [1, 5, 3, 48]:map>

Нека да разгледаме няколко от тях:

e = [0, -1, 3].select     # => #<Enumerator: [0, -1, 3]:select>
e = [0, -1, 3].map        # => #<Enumerator: [0, -1, 3]:map>
e = [0, -1, 3].find       # => #<Enumerator: [0, -1, 3]:find>
e = [0, -1, 3].drop_while # => #<Enumerator: [0, -1, 3]:drop_while>
e = [0, -1, 3].group_by   # => #<Enumerator: [0, -1, 3]:group_by>
e = [0, -1, 3].partition  # => #<Enumerator: [0, -1, 3]:partition>

Всеки път, когато дефинирахме e, получавахме изброител, пружинно зареден с масив и метод. Масивът може да бъде заменен с променлива, което създава гъвкавост, но методът е това, което прави това полезно.

По-голямата част от верижните изброители се привързват към някакъв вариант на #each. Нека да разгледаме някои примери:

number_array = [0, -1, 3]
e = number_array.select
e.each{|i| i >= 0}        # => [0, 3]
e = number_array.map
e.each{|i| i ** i}        # => [1, -1, 27]
e = number_array.find
e.each{|i| i ** i == i}   # => -1
e = number_array.drop_while
e.each{|i| i < 1}         # => [3]
e = number_array.group_by
e.each{|i| (i*3).even?}   # => {true=>[0], false=>[-1, 3]}
e = number_array.partition
e.each{|i| i*5 > 0}       # => [[3], [0, -1]]

Добре, разбирате. Изглежда малко излишно да се използва #each отново, защото първият метод във веригата вече разглежда всяка стойност. Но ние добавяме функционалност, просто това е същата функционалност. Става по-ясно в следващия пример.

name_array = ["Tom", "Jane", "Jon"]
e = name_array.map
e.each_with_index{|name,indx| "#{indx}: #{name}"}
# => ["0: Tom", "1: Jane", "2: Jon"]
e = name_array.find
e.each_with_index{|name,indx| indx+1 == name.length}
# => "Jon"
e = name_array.drop_while
e.each_with_index{|name,indx| indx < 1}
# => ["Jane", "Jon"]
e = name_array.group_by
e.each_with_index{|name,indx| indx.even?}
# => {true=>["Tom", "Jon"], false=>["Jane"]}
e = name_array.partition
e.each_with_index{|name,indx| indx**indx > 1}
# => [["Jon"], ["Tom", "Jane"]]

Нека анализираме първия пример:

name_array = ["Tom", "Jane", "Jon"]
e = name_array.map
e.each_with_index{|name,indx| "#{indx}: #{name}"}
# => ["0: Tom", "1: Jane", "2: Jon"]

Първата стойност в name_array се предава през map, но няма блок! Така че продължава към #each_with_index, което наистина има блок. Там се манипулира и се връща в #map. След това #map актуализира масив и предава следващата стойност. Когато няма повече стойности, се връща актуализираният масив.

Това е по-готино, защото можете в известен смисъл да разширите функционалността на вашите методи. Това е полезно. Само не забравяйте, че променливата на изброителя не е необходима, освен ако не планирате да я използвате другаде. Пример:

e = name_array.partition
e.each_with_index{|name,indx| indx**indx > 1}

Е същото като:

name_array.partition.each_with_index{|name,indx| indx**indx > 1}

Освен добавената функционалност, голяма полза от верижните изброители е, когато използвате пространството от имена lazy (страхотна статия за използването на lazy). За да използвате примера в статията, той работи по следния начин:

range =  1..Float::INFINITY   # creates range from 1 to ∞.
                              # In other words, a range
                              # impossible to enumerate over.
p range.lazy.collect {|x| x*x}.first(10)
#  => [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Работи така. Lazy намира най-малкия необходим брой във веригата и пропуска само толкова:

Другите методи на класа на изброителя, като #feed, #inspect, #next, #peek, #peek_values ​​и #rewind очевидно също се нуждаят от #yield. Но тяхното внедряване изглежда много като че работите с Fiber обект, тъй като сте на много ниско ниво с всяка итерация. Можете да ги използвате в тест, например.

Пример:

enum = @list.each

    enum.next.must_equal(3)
    enum.next.must_equal(4)
    enum.next.must_equal(7)
    enum.next.must_equal(13)
    enum.next.must_equal(42)

източник: Статия

Надявам се, че това е било полезно като въведение в класа enumerator и верижното свързване на изброими методи като цяло.