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