Перейти к содержанию

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]]