Всички обичаме безброй методи:
animals = ["cat","dog","iguana"] animals.any?{|word| word.length > 3} #=> True
Изброимите методи избират всеки елемент от масив (или колекция) и предават този елемент на някаква процедура (като умножение или друг метод).
Нека да разгледаме „документацията“ за някои изброими методи:
map { |obj| блок } → масив
карта → изброител
Очевидно има два начина за използване на #map. Нека го използваме и по двата начина.
- Използвайте номер едно:
[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 и верижното свързване на изброими методи като цяло.