Ruby/Справочник/Enumerable

Примесь Enumerable править

Примесь Enumerable обеспечивает классы коллекций методами итерации(обхода), поиска и сортировки значений. Класс реализующий Enumerable должен определить метод each который вызывает (в yield) последовательно элементы коллекции. Если Enumerable#max, #min, или #sort необходимы, тогда объекты коллекции должны реализовывать оператор сравнения <=>, так как эти методы зависят от порядка элементов коллекции между друг другом.


Методы объекта

all?, any?, collect, detect, each_cons, each_slice, each_with_index, entries, enum_cons, enum_slice, enum_with_index, find_all, find, grep, group_by, include?, index_by, inject, map, max, member?, min, partition, reject, select, sort_by, sort, sum, to_a, to_set, zip

Enumerable#all? править


 enum.all? [{|obj| block } ]   => true or false

Передаёт в указанный блок каждый элемент коллекции. Метод возвращает true если блок ни разу не вернул false или nil. Если блок не задан, Руби добавляет неявный блок {|obj| obj} (в результате чего all? вернёт true только при условии, если ни один из элементов коллекции не оказался ни false ни nil.)

  %w{ ant bear cat}.all? {|word| word.length >= 3}   #=> true
  %w{ ant bear cat}.all? {|word| word.length >= 4}   #=> false
  [ nil, true, 99 ].all?                             #=> false

Enumerable#any? править


 enum.any? [{|obj| block } ]   => true or false

Передаёт в указанный блок каждый элемент коллекции. Метод вернёт true если блок хотя бы один раз вернул значение, отличное от false или nil. Если блок не задан, Руби добавляет неявный блок {|obj| obj} (в результате чего any? вернёт true если по крайней мере один из элементов коллекции не окажется ни false ни nil.

  %w{ ant bear cat}.any? {|word| word.length >= 3}   #=> true
  %w{ ant bear cat}.any? {|word| word.length >= 4}   #=> true
  [ nil, true, 99 ].any?                             #=> true

Enumerable#collect править


 enum.collect {| obj | block }  => array
 enum.map     {| obj | block }  => array

Возвращает новый массив с результатом обработки блоком каждого элемента в исходном массиве enum.

  (1..4).collect {|i| i*i }   #=> [1, 4, 9, 16]
  (1..4).collect { "cat"  }   #=> ["cat", "cat", "cat", "cat"]

Enumerable#detect править


 enum.detect(ifnone = nil) {| obj | block }  => obj or nil
 enum.find(ifnone = nil)   {| obj | block }  => obj or nil

Передаёт в указанный блок каждый элемент коллекции enum. Возвращает первый элемент enum, для которого результат выполнения block не является false. Если не найдено ни одного соответствия, возвращается значение параметра ifnone (если определён) либо nil

  (1..10).detect  {|i| i % 5 == 0 and i % 7 == 0 }   #=> nil
  (1..100).detect {|i| i % 5 == 0 and i % 7 == 0 }   #=> 35

Enumerable#each_cons править


 each_cons(n) {...}

Выполняет указанный блок для каждого массива, составляемого из набора <n> последовательных элементов коллекции. Например:

   (1..10).each_cons(3) {|a| p a}
   # выведет следующее
   [1, 2, 3]
   [2, 3, 4]
   [3, 4, 5]
   [4, 5, 6]
   [5, 6, 7]
   [6, 7, 8]
   [7, 8, 9]
   [8, 9, 10]

Enumerable#each_slice править


 e.each_slice(n) {...}

Выполняет указанный блок для каждого подмассива из <n> элементов. Например:

   (1..10).each_slice(3) {|a| p a}
   # outputs below
   [1, 2, 3]
   [4, 5, 6]
   [7, 8, 9]
   [10]

Enumerable#each_with_index править


 enum.each_with_index {|obj, i| block }  -> enum

Для каждого элемента enum вызывает block с двумя аргументами - самим элементом и его индексом.

  hash = Hash.new
  %w(cat dog wombat).each_with_index {|item, index|
    hash[item] = index
  }
  hash   #=> {"cat"=>0, "wombat"=>2, "dog"=>1} 
    arr_result = []
   arr = ['cat', 'dog', 'wombat', 'horse']
   arr.each_with_index {|item, index| 
     arr_result << [item, index] 
   }
   arr_result #=> [ ['cat', 0], ['dog', 1], ['wombat', 2], ['horse', 3] ] 

Enumerable#entries править


enum.to_a      #=>    array
enum.entries   #=>    array

Возвращает массив, содержащий элементы enum.

(1..7).to_a                       #=> [1, 2, 3, 4, 5, 6, 7]
{ 'a'=>1, 'b'=>2, 'c'=>3 }.to_a   #=> [["a", 1], ["b", 2], ["c", 3]]

Enumerable#enum_cons править


 e.enum_cons(n)

Возвращает Enumerable::Enumerator.new(self, :each_cons, n).

Enumerable#enum_slice править


 e.enum_slice(n)

Возвращает Enumerable::Enumerator.new(self, :each_slice, n).

Enumerable#enum_with_index править


 enum_with_index

Возвращает Enumerable::Enumerator.new(self, :each_with_index).

Enumerable#find править


 enum.detect(ifnone = nil) {| obj | block }  => obj or nil
 enum.find(ifnone = nil)   {| obj | block }  => obj or nil

Передает в block каждый элемент enum. Возвращает первый элемент, для которого результат обработки block не false. Если не найдено ни одного соответствия, возвращается значение параметра ifnone (если определён) либо nil

  (1..10).detect  {|i| i % 5 == 0 and i % 7 == 0 }   #=> nil
  (1..100).detect {|i| i % 5 == 0 and i % 7 == 0 }   #=> 35

Enumerable#find_all править


 enum.find_all {| obj | block }  => array
 enum.select   {| obj | block }  => array

Возвращает массив, содержащий все элементы enum, для которых результат обработки block не является false (см.также Enumerable#reject).

  (1..10).find_all {|i|  i % 3 == 0 }   #=> [3, 6, 9]

Enumerable#grep править


 enum.grep(pattern)                   => array
 enum.grep(pattern) {| obj | block }  => array

Для каждого элемента enum возвращает массив, для элементов которого выполняется условие Pattern === element. Если задан необязательный block, каждый элемент возвращённого массива обрабатывается блоком и сохраняется в возвращённом массиве.

  (1..100).grep 38..44   #=> [38, 39, 40, 41, 42, 43, 44]
  c = IO.constants
  c.grep(/SEEK/)         #=> ["SEEK_END", "SEEK_SET", "SEEK_CUR"]
  res = c.grep(/SEEK/) {|v| IO.const_get(v) }
  res                    #=> [2, 0, 1]

Enumerable#group_by править


 group_by() {|element| ...}

Собирает Enumerable во множества, сгруппированные по результату обработки блоком. Применим, к примеру, для группирования записей по дате. Например:

 latest_transcripts.group_by(&:day).each do |day, transcripts|
   p "#{day} -> #{transcripts.map(&:class) * ', '}"
 end
 "2006-03-01 -> Transcript"
 "2006-02-28 -> Transcript"
 "2006-02-27 -> Transcript, Transcript"
 "2006-02-26 -> Transcript, Transcript"
 "2006-02-25 -> Transcript"
 "2006-02-24 -> Transcript, Transcript"
 "2006-02-23 -> Transcript"

Enumerable#include? править


 enum.include?(obj)     => true or false
 enum.member?(obj)      => true or false

Возвращает true если хотя бы один из элементов enum окажется равен obj. Равенство проверяется оператором ==.

  IO.constants.include? "SEEK_SET"          #=> true
  IO.constants.include? "SEEK_NO_FURTHER"   #=> false

Enumerable#index_by править


 index_by() {|elem| ...}

Преобразует Enumerable в Hash. Например:

 people.index_by(&:login)
   => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
 people.index_by { |person| "#{person.first_name} #{person.last_name}" }
   => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}

Enumerable#inject править


 enum.inject(initial) {| memo, obj | block }  => obj
 enum.inject          {| memo, obj | block }  => obj

Обрабатывает элементы enum применяя к ним блок, принимающий два параметра - аккумулятор (memo) и обрабатываемый элемент. На каждом шаге аккумулятору memo присваивается значение, возвращенное блоком. Первая форма позволяет присвоить аккумулятору некоторое исходное значение. Вторая форма в качестве исходного значения аккумулятора использует первый элемент коллекции (пропуская этот элемент при проходе).

  # Сложение нескольких чисел
  (5..10).inject {|sum, n| sum + n }              #=> 45
  # Умножение нескольких чисел
  (5..10).inject(1) {|product, n| product * n }   #=> 151200
  # Нахождение наиболее длинного слова
  longest = %w{ cat sheep bear }.inject do |memo,word|
     memo.length > word.length ? memo : word
  end
  longest                                         #=> "sheep"
  # Вычисление длины наиболее длинного слова
  longest = %w{ cat sheep bear }.inject(0) do |memo,word|
     memo >= word.length ? memo : word.length
  end
  longest                                         #=> 5

Enumerable#map править


 enum.collect {| obj | block }  => array
 enum.map     {| obj | block }  => array

Возвращает новый массив с результатами однократной обработки блоком block каждого элемента enum.

  (1..4).collect {|i| i*i }   #=> [1, 4, 9, 16]
  (1..4).collect { "cat"  }   #=> ["cat", "cat", "cat", "cat"]

Enumerable#max править


 enum.max                   => obj
 enum.max {|a,b| block }    => obj

Возвращает элемент enum с максимальным значением. Первая форма предполагает что все элементы являются Comparable. Вторая использует блок, возвращающий a <=> b.

  a = %w(albatross dog horse)
  a.max                                  #=> "horse"
  a.max {|a,b| a.length <=> b.length }   #=> "albatross"

Enumerable#member? править


 enum.include?(obj)     => true или false
 enum.member?(obj)      => true или false

Возвращает true если один из членов перечисления равен obj. Соответствие проверяется с использованием ==.

  IO.constants.include? "SEEK_SET"          #=> true
  IO.constants.include? "SEEK_NO_FURTHER"   #=> false

Enumerable#min править


 enum.min                    => obj
 enum.min {| a,b | block }   => obj

Возвращает элемент enum с минимальным значением. Первая форма предполагает что все элементы являются Comparable. Вторая использует блок, возвращающий a <=>

  a = %w(albatross dog horse)
  a.min                                  #=> "albatross"
  a.min {|a,b| a.length <=> b.length }   #=> "dog"

Enumerable#partition править


 enum.partition {| obj | block }  => [ true_array, false_array ]

Возвращает два массива. Первый содержит элементы enum, для которых результат обработки блока является true, а второй - все остальные элементы enum.

  (1..6).partition {|i| (i&1).zero?}   #=> [[2, 4, 6], [1, 3, 5]]

Enumerable#reject править


 enum.reject {| obj | block }  => array

Возвращает массив, содержащий все элементы enum, для которых результат обработки block является false (см.также Enumerable#find_all). Иными словами выталкивает из массива все элементы обработав которые блок вернет истину. Избавиться от свидетелей.

  (1..10).reject {|i|  i % 3 == 0 }   #=> [1, 2, 4, 5, 7, 8, 10]

Enumerable#select править


 enum.find_all {| obj | block }  => array
 enum.select   {| obj | block }  => array

Возвращает массив, содержащий все элементы enum, для которых результат обработки block не является false (см.также Enumerable#reject).

  (1..10).find_all {|i|  i % 3 == 0 }   #=> [3, 6, 9]

Enumerable#sort править


 enum.sort                     => array
 enum.sort {| a, b | block }   => array

Возвращает массив элементов enum, отсортированных либо собственным методом <=>, либо обработанных блоком. Блок возвращает -1, 0, or +1 в зависимости от результата сравнения a и b. Т.к. в Ruby 1.8, метод Enumerable#sort_by реализован на основе преобразования Шварца, он полезен для вычисления ключей или когда операции сравнения весьма ресурсоёмки.

  %w(rhea kea flea).sort         #=> ["flea", "kea", "rhea"]
  (1..10).sort {|a,b| b <=> a}   #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

Enumerable#sort_by править


 enum.sort_by {| obj | block }    => array

Сортирует enum используя набор ключей, полученных обработкой элементов enum заданным блоком.

  %w{ apple pear fig }.sort_by {|word| word.length}
               #=> ["fig", "pear", "apple"]

Текущая реализация sort_by генерирует массив кортежей, содержащих исходную коллекцию элементов и вычисленное значение. Это делает метод sort_by существенно затратным когда наборы ключей просты.

  require 'benchmark'
  include Benchmark
  
  a = (1..100000).map {rand(100000)}
  
  bm(10) do |b|
    b.report("Sort")    { a.sort }
    b.report("Sort by") { a.sort_by {|a| a} }
  end

вывод:

  user     system      total        real
  Sort        0.180000   0.000000   0.180000 (  0.175469)
  Sort by     1.980000   0.040000   2.020000 (  2.013586)

Однако рассмотрим случай, когда сравнение ключей является нетривиальной операцией. Следующий код сортирует несколько файлов по времени модификации используя основной метод sort.

  files = Dir["*"]
  sorted = files.sort {|a,b| File.new(a).mtime <=> File.new(b).mtime}
  sorted   #=> ["mon", "tues", "wed", "thurs"]

Такая сортирвка неэффективна: для каждой операции сравнения генерируется два новых объекта File. Немного лучший способ - использовать метод Kernel#test для генерации времени модификации напрямую.

  files = Dir["*"]
  sorted = files.sort { |a,b|
    test(?M, a) <=> test(?M, b)
  }
  sorted   #=> ["mon", "tues", "wed", "thurs"]

Это всё ещё порождает множество ненужных объектов Time. Более эффективный метод - кеширование ключей сортировки (в данном случае времени модификации) перед сортировкой. Пользователи Perl часто называют этот метод преобразованием Шварца. Мы создаем временный массив, в котором каждый элемент - это массив, содержащий наши ключи сортировки и имя файла. Сортируем этот массив и извлекаем из результата имя файла.

  sorted = Dir["*"].collect { |f|
     [test(?M, f), f]
  }.sort.collect { |f| f[1] }
  sorted   #=> ["mon", "tues", "wed", "thurs"]

Это в точности то, что реализовано внутри sort_by.

  sorted = Dir["*"].sort_by {|f| test(?M, f)}
  sorted   #=> ["mon", "tues", "wed", "thurs"]

Enumerable#sum править


 sum(identity = 0, &block)

Вычисляет сумму элементов. напимер:

payments.sum { |p| p.price * p.tax_rate }
payments.sum(&:price)

Пример выше равнозначен payments.inject { |sum, p| sum + p.price } Также вычисляет суммы без использования блока:

 [5, 15, 10].sum # => 30

Значение по умолчанию (сумма пустого списка) равна нулю. Но это можно переопределить:

   [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)

Enumerable#to_a править


 enum.to_a      =>    array
 enum.entries   =>    array

Возвращает массив, содержащий элементы enum.

  (1..7).to_a                       #=> [1, 2, 3, 4, 5, 6, 7]
  { 'a'=>1, 'b'=>2, 'c'=>3 }.to_a   #=> [["a", 1], ["b", 2], ["c", 3]]

Enumerable#to_set править


 to_set(klass = Set, *args, &block)

Создает набор из Enumerable объекта с указанными аргументами. Для использования метода необходим require "set".

Enumerable#zip править


 enum.zip(arg, ...)                   => array
 enum.zip(arg, ...) {|arr| block }    => nil

Преобразует любые элементы в массивы, затем объединяет элементы enum с соответствующими элементами каждого из аргументов. Генерируется последовательность enum#size n-элементных массивов, где n - количество аргументов (1 или более). Если размер любого аргумента менее enum#size, то присваивается nil. Если задан блок, то он обрабатывает каждый результирующий массив. Иначе возвращается массив массивов.

  a = [ 4, 5, 6 ]
  b = [ 7, 8, 9 ]
  (1..3).zip(a, b)      #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
  "cat\ndog".zip([1])   #=> [["cat\n", 1], ["dog", nil]]
  (1..3).zip            #=> [[1], [2], [3]]