Итераторы ruby
Итераторы - это методы которые принимают блок, используются вместо циклов, потому что они более компактны и функциональны.
Находятся в модуле Enumerable.
Enumerable предоставляет очень мощные методы для работы с массивами и хешами, которые называются итераторами.
Итератор — это метод, который обходит коллекцию поэлементно выполняя с каждым элементом определенную операцию. Если быть совсем честным, то итератор, в большинстве случаев не сам выполняет работу, а передает каждый элемент коллекции в блок кода, где, чаще всего и происходит основная работа. Запомните итераторы повышают читаемость кода.
Существует два вида как обозначается блок.
Если блок однострочный, то используют фигурные скобки { }
Если блок будет содержать несколько строк кода, то используют конструкцию do ... end
Вот основные методы итераторы:
Итераторы для числового типа данных
#times — выполняет блок кода N-число раз, где N — число для которого был вызван данный метод:
5.times {|i| print i, ", "} #0, 1, 2, 3, 4, => 5
#upto - Аналогичен циклу for, выполняется N до M раз.
5.upto(10) { |i| print "#{i} " } #=> 5 6 7 8 9
#step — данный очень похож на метод #upto с тем отличием, что имеется возможность задавать шаг хода, например:
1.step(20,2){|x| print x,", "} #1, 3, 5, 7, 9, 11, 13, 15, 17, 19, => 1
В руби 2.0 появились ключевые слова в качестве аргументов:
Что значительно улучшает читаемость кода;
например можно писать
1.step(by: 2, to: 20){|x| print x,", "} #1, 3, 5, 7, 9, 11, 13, 15, 17, 19, => 1
Итерация по диапазону
#each — сей метод позволяет вам проходить по каждому элементу входящему в диапазон и выполнять с ним определенные действия, например:
(1..10).each{|e| print e, ', '} #1, 2, 3, 4, 5, 6, 7, 8, 9, 10, => 1..10
#step — данный метод нам уже знаком, так как мы уже сталкивались с ним, когда разбирались с целыми числами. Его отличие от целочисленного #step состоит в том, что обе границы заданы и нам необходимо задать лишь шаг итерации, пример:
('a'..'z').step(2){|ch| print ch, ", "} # a, c, e, g, i, k, m, o, q, s, u, w, y, => "a".."z"
Итераторы для массивов и хешей
#each — данный итератор пробегает по коллекции и передает каждый ее элемент в блок кода, где над ними происходит определенное действие, примеры:
a = [1,2,3,4,5]
h = {}
a.each{|value| h[value] = value**2} #=> [1, 2, 3, 4, 5]
h #=> {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
(1..5).each{|v| print v**2, " "} #=> 1 4 9 16 25
Метод #each для хэшей имеет некоторое отличие от аналога для массивов — он может принимать не только значения, но и индексы, пример:
h = {a: 100, b:200, c:300, d:400}
h.each{|value| print value, " "} #=>[:a, 100] [:b, 200] [:c, 300] [:d, 400]
h.each{|key, value| print key, "-> ", value, " " } #=> a-> 100 b-> 200 c-> 300 d-> 400
#each_with_index — данные метод предоставляет доступ не только к значениям элементов, но и их индексам, пример:
a.each_with_index{|value, key| a[key] = value ** 2} #=> [1, 4, 9, 16, 25]
a #=> [1, 4, 9, 16, 25]
При использовании #each_with_index вместе с диапазонами, порядок следования аргументов блока (ключа и значения) меняется, пример:
(1..5).each_with_index{|key, value| print v, "-", k, " "}
0-1 1-2 2-3 3-4 4-5
При выполнении #each_with_index для хэша происходит его преобразование в массив, пример:
h = {a: 100, b: 200, c: 300, d: 400}
h.each_with_index{|v, k| print "#{k} -> #{v} "}
0 -> [:a, 100] 1 -> [:b, 200] 2 -> [:c, 300] 3 -> [:d, 400]
#collect и #map — методы синонимы, которые используются для создания новой коллекции из уже имеющейся, примеры:
a = [1,2,3,4,5]
a2 = a.collect{|v| v**2}
a2 #=> [1, 4, 9, 16, 25]
#collect и #map используются для создания коллекций, где корнем является массив, пример:
h2 = h.collect{|k, v| {"#{v}#{k}"=> v**2}}
#=> [{"100a"=>10000}, {"200b"=>40000}, {"300c"=>90000}, {"400d"=>160000}]
Для того, чтобы получить хэш без вложенных коллекций следует использовать уже знакомый нам способ создания хеша при помощи Hash[] и метод #flatten:
h2 = Hash[*h.collect{|k, v| ["#{v}#{k}", v**2]}.flatten]
#=> {"100a"=>10000, "200b"=>40000, "300c"=>90000, "400d"=>160000}
#count — данный метод мы уже рассматривали в разделе посвященном массивам, однако давайте послушаемся старой пословицы «Повторение — мать учения»:
h #=> {:a=>100, :b=>200, :c=>300, :d=>400}
h.count{|k,v| v > 200} #=> 2
h.count{|k,v| v >= 200} #=> 3
a #=> [1, 2, 3, 4, 5]
a.count{|v| v % 2 == 0} #=> 2
(0..10).count{|v| v**2 > 16} #=> 6
#any? и #all? -данные методы несколько отличаются от остальных итераторов тем, что не возвращают коллекцию, а возвращают булево значение true или false. Эти методы проходят по элементам коллекции и передают каждый из них в блок кода, который выполняет проверку. Метод #any? возвращает true, если хотя бы для одного элемента коллекции блок кода возвращает true, в противном случае будет возвращено значение false, метод #all? возвращает true только когда для всех элементов коллекции условие в блоке кода возвращает true, иначе возвращает false, примеры:
a #=> [1, 2, 3, 4, 5]
a.any?{|v| v > 4} #=> true
a.all?{|v| v > 4} #=> false
a.any?{|v| v > 0} #=> true
a.all?{|v| v > 0} #=> true
h #=> {:a=>100, :b=>200, :c=>300, :d=>400}
h.all?{|k,v| v > 100} #=> false
h.any?{|k,v| v > 100} #=> true
h.any?{|k,v| k.equal? :b} #=> true
(0...100).any?{|v| v > 100} #=> false
(0...100).all?{|v| v < 100} #=> true
#find и #detect — данные итераторы являются синонимами. Я рекомендую вам использовать метод #detect, поскольку программируя на Rails метод #find у вас будет переписан одноименным методом из ActiveRecord. Методы #find и #detect используются для получения первого элемента коллекции который соответствует условию в блоке кода, примеры:
a #=> [1, 2, 3, 4, 5]
a.detect{|v| v >= 3} #=> 3
h #=> {:a=>100, :b=>200, :c=>300, :d=>400}
h.detect{|k,v| v % 2 == 0} #=> [:a, 100]
h.detect{|k,v| v % 15 == 0} #=> [:c, 300]
(0..10).detect{|v| v**2 == v} #=> 0
#find_all, #select — данные методы выполняют ту жеработу, что и #find и #detect, однако возвращают не первый элемент, который соответствует условию, а массив всех соответствующих условию элементов:
a #=> [1, 2, 3, 4, 5]
a.find_all{|v| v >= 3} #=> [3, 4, 5]
h #=> {:a=>100, :b=>200, :c=>300, :d=>400}
h.select{|k,v| k.instance_of?(Symbol)} #=> {:a=>100, :b=>200, :c=>300, :d=>400}
(0..100).select{|v| v > 30 and v < 40} #=> [31, 32, 33, 34, 35, 36, 37, 38, 39]
#each_char — данные итератор принадлежит строковым объектам и передается при каждой итерации по одному символу в блок кода, пример:
str = ""
"hello readers!".chars{|char| str << char.upcase}
str #=> "HELLO READERS!"