module Enumerable
Что здесь есть
Модуль Enumerable предоставляет методы, полезные для класса коллекции, для выполнения следующих задач:
Методы для запросов
Эти методы возвращают сведения об Enumerable, отличные от самих элементов:
-
member?(также доступен под именемinclude?): Возвращаетtrue, еслиself == object, иfalseв противном случае. -
all?: Возвращаетtrue, если все элементы удовлетворяют заданному условию; в противном случае возвращаетfalse. -
any?: Возвращаетtrue, если хотя бы один элемент удовлетворяет заданному условию; в противном случае возвращаетfalse. -
none?: Возвращаетtrue, если ни один элемент не удовлетворяет заданному условию; в противном случае возвращаетfalse. -
one?: Возвращаетtrue, если заданному условию удовлетворяет ровно один элемент; в противном случае возвращаетfalse. -
count: Возвращает количество элементов, при наличии аргумента или блока учитывая заданное в нём условие. -
tally: Возвращает новыйHash, содержащий количество вхождений каждого элемента.
Методы для получения элементов
Эти методы возвращают элементы Enumerable, не изменяя его:
Первые, последние или все элементы:
-
to_a(также доступен под именемentries): Возвращает все элементы. -
first: Возвращает первый элемент или первые элементы. -
take: Возвращает указанное количество первых элементов. -
drop: Возвращает указанное количество последних элементов. -
take_while: Возвращает первые элементы, выбранные в соответствии с заданным блоком. -
drop_while: Возвращает последние элементы, выбранные в соответствии с заданным блоком.
Элементы с минимальным и максимальным значением:
-
min: Возвращает элементы с наименьшими значениями, определяемыми с помощью <=> или заданного блока. -
max: Возвращает элементы с наибольшими значениями, определяемыми с помощью <=> или заданного блока. -
minmax: ВозвращаетArrayиз 2 элементов, содержащий наименьший и наибольший элементы. -
min_by: Возвращает наименьший элемент, определяемый заданным блоком. -
max_by: Возвращает наибольший элемент, определяемый заданным блоком. -
minmax_by: Возвращает наименьший и наибольший элементы, определяемые заданным блоком.
Группы, фрагменты и разделение на части:
-
group_by: ВозвращаетHash, распределяющий элементы по группам. -
partition: Возвращает элементы, разделённые на два новых массива согласно заданному блоку. -
slice_after: Возвращает новыйEnumerator, элементы которого представляют собой разделённые на частиselfна основе заданногоobjectили блока. -
slice_before: Возвращает новыйEnumerator, элементы которого представляют собой разделённые на частиselfна основе заданногоobjectили блока. -
slice_when: Возвращает новыйEnumerator, элементы которого представляют собой разделённые на частиselfна основе заданного блока. -
chunk: Возвращает элементы, сгруппированные в фрагменты согласно заданному блоку. -
chunk_while: Возвращает элементы, сгруппированные в фрагменты согласно заданному блоку.
Методы для поиска и фильтрации
Эти методы возвращают элементы, удовлетворяющие заданному условию:
-
find(также доступен под именемdetect): Возвращает элемент, выбранный блоком. -
find_all(также доступны под именамиfilter,select): Возвращает элементы, выбранные блоком. -
find_index: Возвращает индекс элемента, выбранного заданным объектом или блоком. -
reject: Возвращает элементы, не отклонённые блоком. -
uniq: Возвращает элементы, не являющиеся дубликатами.
Методы для сортировки
Эти методы возвращают элементы в отсортированном порядке:
-
sort: Возвращает элементы, отсортированные с помощью <=> или заданного блока. -
sort_by: Возвращает элементы, отсортированные с помощью заданного блока.
Методы для итерации
-
each_entry: Вызывает блок для каждого следующего элемента (незначительно отличается от each). -
each_with_index: Вызывает блок для каждого следующего элемента и его индекса. -
each_with_object: Вызывает блок для каждого следующего элемента и заданного объекта. -
each_slice: Вызывает блок для последовательных неперекрывающихся фрагментов. -
each_cons: Вызывает блок для последовательных перекрывающихся фрагментов (отличается отeach_slice). -
reverse_each: Вызывает блок для каждого следующего элемента в обратном порядке.
Другие методы
-
collect(также доступен под именемmap): Возвращает объекты, возвращённые блоком. -
filter_map: Возвращает объекты, считающиеся истинными, которые возвращены блоком. -
flat_map(также доступен под именемcollect_concat): Возвращает развёрнутые объекты, возвращённые блоком. -
grep: Возвращает элементы, выбранные заданным объектом, или объекты, возвращённые заданным блоком. -
grep_v: Возвращает элементы, не выбранные заданным объектом, или объекты, возвращённые заданным блоком. -
inject(также доступен под именемreduce): Возвращает объект, полученный объединением всех элементов. -
sum: Возвращает сумму элементов, используя метод+. -
zip: Объединяет каждый элемент с элементами других перечислимых объектов; возвращает n-кортежи или вызывает для каждого элемента блок. -
cycle: Многократно вызывает блок для каждого элемента по кругу.
Использование
Чтобы использовать модуль Enumerable в классе коллекции:
-
Подключите его:
include Enumerable
-
Реализуйте метод each, который должен передавать последовательно элементы коллекции. Этот метод вызывается почти всеми методами Enumerable.
Пример:
class Foo
include Enumerable
def each
yield 1
yield 1, 2
yield
end
end
Foo.new.each_entry{ |element| p element }
Вывод:
1 [1, 2] nil
Enumerable в классах Ruby
Следующие базовые классы Ruby подключают (или расширяют) Enumerable:
Следующие классы стандартной библиотеки Ruby подключают Enumerable:
-
CSV
-
CSV::Table
-
CSV::Row
-
Set
Практически все методы Enumerable вызывают метод each класса, подключающего модуль:
-
Hash#eachпередаёт следующую пару «ключ — значение» в виде 2-элементногоArray. -
Struct#eachпередаёт следующую пару «имя — значение» в виде 2-элементногоArray. -
Для остальных перечисленных выше классов each передаёт следующий объект коллекции.
Об примерах
Примеры кода для методов Enumerable:
-
Всегда демонстрируют использование одного или нескольких классов, подобных Array (часто самого
Array). -
Иногда демонстрируют использование класса, подобного Hash. Однако для некоторых методов такой пример не имел бы смысла, поэтому он не приводится. Например,
tallyнашёл бы ровно по одному вхождению каждой записиHash.
Расширенные методы
Класс Enumerable может определять расширенные методы. В этом разделе для справки описано стандартное поведение методов расширения.
size
У Enumerator есть метод size. Он использует функцию размера, переданную в Enumerator.new.
e = Enumerator.new(-> { 3 }) {|y| p y; y.yield :a; y.yield :b; y.yield :c; :z }
p e.size #=> 3
p e.next #=> :a
p e.next #=> :b
p e.next #=> :c
begin
e.next
rescue StopIteration
p $!.result #=> :z
end
Результат функции размера должен обозначать количество итераций (то есть количество вызовов Enumerator::Yielder#yield). В приведённом выше примере блок вызывает yield три раза, и функция размера +-> { 3 }+ соответственно возвращает 3. Результатом функции размера может быть целое число, Float::INFINITY или nil. Целое число означает точное количество вызовов yield, как показано выше. Float::INFINITY указывает на бесконечное количество вызовов yield. nil означает, что количество вызовов yield трудно или невозможно определить.
Многие методы итерации, если блок не задан, возвращают объект Enumerator с подходящей функцией размера.
Примеры:
["a", "b", "c"].each.size #=> 3
{a: "x", b: "y", c: "z"}.each.size #=> 3
(0..20).to_a.permutation.size #=> 51090942171709440000
loop.size #=> Float::INFINITY
(1..100).drop_while.size #=> nil # size depends on the block's behavior
STDIN.each.size #=> nil # cannot be computed without consuming input
File.open("/etc/resolv.conf").each.size #=> nil # cannot be computed without reading the file
Поведение size у перечислителей, основанных на Range, зависит от начального элемента:
-
Если начальный элемент — это
Integer, метод size возвращаетIntegerилиFloat::INFINITY. -
Если у начального элемента есть метод succ (кроме
Integer), size возвращаетnil. (Вычисление размера потребовало бы многократного вызова succ, что может быть слишком медленно.) -
Если у начального элемента нет метода succ, size вызывает исключение
TypeError.
Примеры:
(10..42).each.size #=> 33
(10..42.9).each.size #=> 33 (the #end element may be a non-integer numeric)
(10..).each.size #=> Float::INFINITY
("a".."z").each.size #=> nil
("a"..).each.size #=> nil
(1.0..9.0).each.size # raises TypeError (Float does not have #succ)
(..10).each.size # raises TypeError (beginless range has nil as its #begin)
Сам модуль Enumerable не определяет метод size. Класс, подключающий Enumerable, может определить собственный метод size. Рекомендуется, чтобы такой метод size согласовывался с Enumerator#size.
Array и Hash реализуют size и возвращают значения, согласующиеся с Enumerator#size. IO и Dir не определяют size, что также согласуется с поведением, поскольку функция размера соответствующего перечислителя возвращает nil.
Однако метод size класса не обязательно должен в точности соответствовать Enumerator#size. Например, File#size возвращает количество байтов в файле, а не количество строк.
Открытые методы экземпляра
static VALUE
enum_all(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
WARN_UNUSED_BLOCK(argc);
ENUM_BLOCK_CALL(all);
return memo->v1;
} Возвращает, удовлетворяют ли все элементы заданному критерию.
Если self не содержит элементов, возвращает true; аргумент и блок не используются.
Если аргумент и блок не указаны, возвращает, являются ли все элементы истинными значениями:
(1..4).all? # => true %w[a b c d].all? # => true [1, 2, nil].all? # => false ['a','b', false].all? # => false [].all? # => true
Если указан аргумент pattern, но блок не задан, возвращает, выполняется ли для каждого элемента условие element, pattern === element:
(1..4).all?(Integer) # => true
(1..4).all?(Numeric) # => true
(1..4).all?(Float) # => false
%w[bar baz bat bam].all?(/ba/) # => true
%w[bar baz bat bam].all?(/bar/) # => false
%w[bar baz bat bam].all?('ba') # => false
{foo: 0, bar: 1, baz: 2}.all?(Array) # => true
{foo: 0, bar: 1, baz: 2}.all?(Hash) # => false
[].all?(Integer) # => true
Если задан блок, возвращает, возвращает ли блок истинное значение для каждого элемента:
(1..4).all? {|element| element < 5 } # => true
(1..4).all? {|element| element < 4 } # => false
{foo: 0, bar: 1, baz: 2}.all? {|key, value| value < 3 } # => true
{foo: 0, bar: 1, baz: 2}.all? {|key, value| value < 2 } # => false
static VALUE
enum_any(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qfalse);
WARN_UNUSED_BLOCK(argc);
ENUM_BLOCK_CALL(any);
return memo->v1;
} Возвращает, удовлетворяет ли какой-либо элемент заданному критерию.
Если self не содержит элементов, возвращает false; аргумент и блок не используются.
Если аргумент и блок не указаны, возвращает, является ли какой-либо элемент истинным значением:
(1..4).any? # => true %w[a b c d].any? # => true [1, false, nil].any? # => true [].any? # => false
Если указан аргумент pattern, но блок не задан, возвращает, выполняется ли для какого-либо элемента условие element, pattern === element:
[nil, false, 0].any?(Integer) # => true
[nil, false, 0].any?(Numeric) # => true
[nil, false, 0].any?(Float) # => false
%w[bar baz bat bam].any?(/m/) # => true
%w[bar baz bat bam].any?(/foo/) # => false
%w[bar baz bat bam].any?('ba') # => false
{foo: 0, bar: 1, baz: 2}.any?(Array) # => true
{foo: 0, bar: 1, baz: 2}.any?(Hash) # => false
[].any?(Integer) # => false
Если задан блок, возвращает, возвращает ли блок истинное значение хотя бы для одного элемента:
(1..4).any? {|element| element < 2 } # => true
(1..4).any? {|element| element < 1 } # => false
{foo: 0, bar: 1, baz: 2}.any? {|key, value| value < 1 } # => true
{foo: 0, bar: 1, baz: 2}.any? {|key, value| value < 0 } # => false
static VALUE
enum_chain(int argc, VALUE *argv, VALUE obj)
{
VALUE enums = rb_ary_new_from_values(1, &obj);
rb_ary_cat(enums, argv, argc);
return new_enum_chain(enums);
} Возвращает объект-перечислитель, созданный на основе этого перечислителя и заданных перечисляемых объектов.
e = (1..3).chain([4, 5]) e.to_a #=> [1, 2, 3, 4, 5]
static VALUE
enum_chunk(VALUE enumerable)
{
VALUE enumerator;
RETURN_SIZED_ENUMERATOR(enumerable, 0, 0, enum_size);
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, id_chunk_enumerable, enumerable);
rb_ivar_set(enumerator, id_chunk_categorize, rb_block_proc());
rb_block_call(enumerator, idInitialize, 0, 0, chunk_i, enumerator);
return enumerator;
} Каждый элемент возвращаемого перечислителя — это массив из двух элементов, состоящий из:
-
Значения, возвращённого блоком.
-
Массива («фрагмента»), содержащего элемент, для которого было возвращено это значение, и все последующие элементы, для которых блок вернул то же значение:
Таким образом:
-
Каждое возвращаемое блоком значение, отличающееся от предыдущего, начинает новый фрагмент.
-
Каждое возвращаемое блоком значение, совпадающее с предыдущим, продолжает тот же фрагмент.
Пример:
e = (0..10).chunk {|i| (i / 3).floor } # => #<Enumerator: ...>
# The enumerator elements.
e.next # => [0, [0, 1, 2]]
e.next # => [1, [3, 4, 5]]
e.next # => [2, [6, 7, 8]]
e.next # => [3, [9, 10]]
Метод chunk особенно полезен для перечисляемого объекта, элементы которого уже отсортированы. В этом примере подсчитывается количество слов на каждую начальную букву в большом массиве слов:
# Get sorted words from a web page.
url = 'https://raw.githubusercontent.com/eneko/data-repository/master/data/words.txt'
words = URI::open(url).readlines
# Make chunks, one for each letter.
e = words.chunk {|word| word.upcase[0] } # => #<Enumerator: ...>
# Display 'A' through 'F'.
e.each {|c, words| p [c, words.length]; break if c == 'F' }
Результат:
["A", 17096] ["B", 11070] ["C", 19901] ["D", 10896] ["E", 8736] ["F", 6860]
Можно использовать специальный символ :_alone, чтобы поместить элемент в отдельный фрагмент:
a = [0, 0, 1, 1]
e = a.chunk{|i| i.even? ? :_alone : true }
e.to_a # => [[:_alone, [0]], [:_alone, [0]], [true, [1, 1]]]
Например, можно поместить каждую строку, содержащую URL, в отдельный фрагмент:
pattern = /http/
open(filename) { |f|
f.chunk { |line| line =~ pattern ? :_alone : true }.each { |key, lines|
pp lines
}
}
Можно использовать специальный символ :_separator или nil, чтобы пропустить элемент (не включать его ни в один фрагмент):
a = [0, 0, -1, 1, 1]
e = a.chunk{|i| i < 0 ? :_separator : true }
e.to_a # => [[true, [0, 0]], [true, [1, 1]]]
Обратите внимание, что разделитель завершает фрагмент:
a = [0, 0, -1, 1, -1, 1]
e = a.chunk{|i| i < 0 ? :_separator : true }
e.to_a # => [[true, [0, 0]], [true, [1]], [true, [1]]]
Например, последовательность дефисов в svn log можно исключить следующим образом:
sep = "-"*72 + "\n"
IO.popen("svn log README") { |f|
f.chunk { |line|
line != sep || nil
}.each { |_, lines|
pp lines
}
}
#=> ["r20018 | knu | 2008-10-29 13:20:42 +0900 (Wed, 29 Oct 2008) | 2 lines\n",
# "\n",
# "* README, README.ja: Update the portability section.\n",
# "\n"]
# ["r16725 | knu | 2008-05-31 23:34:23 +0900 (Sat, 31 May 2008) | 2 lines\n",
# "\n",
# "* README, README.ja: Add a note about default C flags.\n",
# "\n"]
# ...
Абзацы, разделённые пустыми строками, можно обработать следующим образом:
File.foreach("README").chunk { |line|
/\A\s*\z/ !~ line || nil
}.each { |_, lines|
pp lines
}
static VALUE
enum_chunk_while(VALUE enumerable)
{
VALUE enumerator;
VALUE pred;
pred = rb_block_proc();
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, id_slicewhen_enum, enumerable);
rb_ivar_set(enumerator, id_slicewhen_pred, pred);
rb_ivar_set(enumerator, id_slicewhen_inverted, Qtrue);
rb_block_call(enumerator, idInitialize, 0, 0, slicewhen_i, enumerator);
return enumerator;
} Создаёт перечислитель для разбиения элементов на фрагменты. Начало каждого фрагмента определяется блоком.
Этот метод разделяет каждый фрагмент, используя соседние элементы elt_before и elt_after в перечислителе-получателе. Метод разделяет фрагменты между elt_before и elt_after, если блок возвращает false.
Блок вызывается на единицу меньше количества элементов перечислителя-получателя.
Возвращаемый перечислитель выдаёт сгруппированные элементы в виде массива. Поэтому метод each можно вызвать следующим образом:
enum.chunk_while { |elt_before, elt_after| bool }.each { |ary| ... } Можно также использовать другие методы класса Enumerator и модуля Enumerable, такие как to_a, map и т. д.
Например, возрастающую подпоследовательность с шагом один можно разбить на фрагменты следующим образом:
a = [1,2,4,9,10,11,12,15,16,19,20,21]
b = a.chunk_while {|i, j| i+1 == j }
p b.to_a #=> [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
p c #=> [[1, 2], [4], "9-12", [15, 16], "19-21"]
d = c.join(",")
p d #=> "1,2,4,9-12,15,16,19-21"
Возрастающую (неубывающую) подпоследовательность можно разбить на фрагменты следующим образом:
a = [0, 9, 2, 2, 3, 2, 7, 5, 9, 5]
p a.chunk_while {|i, j| i <= j }.to_a
#=> [[0, 9], [2, 2, 3], [2, 7], [5, 9], [5]]
Соседние чётные и нечётные числа можно разбить на фрагменты следующим образом: (метод Enumerable#chunk позволяет добиться того же.)
a = [7, 5, 9, 2, 0, 7, 9, 4, 2, 0]
p a.chunk_while {|i, j| i.even? == j.even? }.to_a
#=> [[7, 5, 9], [2, 0], [7, 9], [4, 2, 0]]
Метод Enumerable#slice_when действует аналогично, но выполняет разделение, когда блок возвращает true, а не false.
static VALUE
enum_collect(VALUE obj)
{
VALUE ary;
int min_argc, max_argc;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
min_argc = rb_block_min_max_arity(&max_argc);
rb_lambda_call(obj, id_each, 0, 0, collect_i, min_argc, max_argc, ary);
return ary;
} static VALUE
enum_compact(VALUE obj)
{
VALUE ary;
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, compact_i, ary);
return ary;
} Возвращает массив, содержащий все элементы, кроме nil:
a = [nil, 0, nil, 'a', false, nil, false, nil, 'a', nil, 0, nil] a.compact # => [0, "a", false, false, "a", 0]
static VALUE
enum_count(int argc, VALUE *argv, VALUE obj)
{
VALUE item = Qnil;
struct MEMO *memo;
rb_block_call_func *func;
if (argc == 0) {
if (rb_block_given_p()) {
func = count_iter_i;
}
else {
func = count_all_i;
}
}
else {
rb_scan_args(argc, argv, "1", &item);
if (rb_block_given_p()) {
rb_warn("given block not used");
}
func = count_i;
}
memo = MEMO_NEW(item, 0, 0);
rb_block_call(obj, id_each, 0, 0, func, (VALUE)memo);
return imemo_count_value(memo);
} Возвращает количество элементов с учётом заданного аргумента или критерия блока, если они указаны.
Если аргумент и блок не заданы, возвращает количество элементов:
[0, 1, 2].count # => 3
{foo: 0, bar: 1, baz: 2}.count # => 3
Если указан аргумент object, возвращает количество элементов, которые == к object:
[0, 1, 2, 1].count(1) # => 2
Если задан блок, вызывает его для каждого элемента и возвращает количество элементов, для которых блок возвращает истинное значение:
[0, 1, 2, 3].count {|element| element < 2} # => 2
{foo: 0, bar: 1, baz: 2}.count {|key, value| value < 2} # => 2
static VALUE
enum_cycle(int argc, VALUE *argv, VALUE obj)
{
VALUE ary;
VALUE nv = Qnil;
long n, i, len;
rb_check_arity(argc, 0, 1);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_cycle_size);
if (!argc || NIL_P(nv = argv[0])) {
n = -1;
}
else {
n = NUM2LONG(nv);
if (n <= 0) return Qnil;
}
ary = rb_ary_new();
RBASIC_CLEAR_CLASS(ary);
rb_block_call(obj, id_each, 0, 0, cycle_i, ary);
len = RARRAY_LEN(ary);
if (len == 0) return Qnil;
while (n < 0 || 0 < --n) {
for (i=0; i<len; i++) {
enum_yield_array(RARRAY_AREF(ary, i));
}
}
return Qnil;
} При вызове с положительным целочисленным аргументом n и блоком вызывает блок для каждого элемента, а затем повторяет это, пока не выполнит вызов n раз; возвращает nil:
a = []
(1..4).cycle(3) {|element| a.push(element) } # => nil
a # => [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
a = []
('a'..'d').cycle(2) {|element| a.push(element) }
a # => ["a", "b", "c", "d", "a", "b", "c", "d"]
a = []
{foo: 0, bar: 1, baz: 2}.cycle(2) {|element| a.push(element) }
a # => [[:foo, 0], [:bar, 1], [:baz, 2], [:foo, 0], [:bar, 1], [:baz, 2]]
Если количество повторений равно нулю или отрицательное, блок не вызывается.
При вызове с блоком и значением n, равным nil, выполняет цикл бесконечно.
Если блок не задан, возвращает Enumerator.
static VALUE
enum_drop(VALUE obj, VALUE n)
{
VALUE result;
struct MEMO *memo;
long len = NUM2LONG(n);
if (len < 0) {
rb_raise(rb_eArgError, "attempt to drop negative size");
}
result = rb_ary_new();
memo = MEMO_NEW(result, 0, len);
rb_block_call(obj, id_each, 0, 0, drop_i, (VALUE)memo);
return result;
} Для положительного целого числа n возвращает массив, содержащий все элементы, кроме первых n:
r = (1..4)
r.drop(3) # => [4]
r.drop(2) # => [3, 4]
r.drop(1) # => [2, 3, 4]
r.drop(0) # => [1, 2, 3, 4]
r.drop(50) # => []
h = {foo: 0, bar: 1, baz: 2, bat: 3}
h.drop(2) # => [[:baz, 2], [:bat, 3]]
static VALUE
enum_drop_while(VALUE obj)
{
VALUE result;
struct MEMO *memo;
RETURN_ENUMERATOR(obj, 0, 0);
result = rb_ary_new();
memo = MEMO_NEW(result, 0, FALSE);
rb_block_call(obj, id_each, 0, 0, drop_while_i, (VALUE)memo);
return result;
} Вызывает блок для последовательных элементов, пока блок возвращает истинное значение; возвращает массив всех элементов, начиная с того, на котором это условие перестало выполняться:
(1..4).drop_while{|i| i < 3 } # => [3, 4]
h = {foo: 0, bar: 1, baz: 2}
a = h.drop_while{|element| key, value = *element; value < 2 }
a # => [[:baz, 2]]
Если блок не задан, возвращает Enumerator.
e = (1..4).drop_while p e #=> #<Enumerator: 1..4:drop_while> i = e.next; p i; e.feed(i < 3) #=> 1 i = e.next; p i; e.feed(i < 3) #=> 2 i = e.next; p i; e.feed(i < 3) #=> 3 begin e.next rescue StopIteration p $!.result #=> [3, 4] end
static VALUE
enum_each_cons(VALUE obj, VALUE n)
{
long size = NUM2LONG(n);
struct MEMO *memo;
int arity;
if (size <= 0) rb_raise(rb_eArgError, "invalid size");
RETURN_SIZED_ENUMERATOR(obj, 1, &n, enum_each_cons_size);
arity = rb_block_arity();
if (enum_size_over_p(obj, size)) return obj;
memo = MEMO_NEW(rb_ary_new2(size), dont_recycle_block_arg(arity), size);
rb_block_call(obj, id_each, 0, 0, each_cons_i, (VALUE)memo);
return obj;
} Вызывает блок для каждого следующего перекрывающегося кортежа из n элементов; возвращает self:
a = []
(1..5).each_cons(3) {|element| a.push(element) }
a # => [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
a = []
h = {foo: 0, bar: 1, baz: 2, bam: 3}
h.each_cons(2) {|element| a.push(element) }
a # => [[[:foo, 0], [:bar, 1]], [[:bar, 1], [:baz, 2]], [[:baz, 2], [:bam, 3]]]
Если блок не задан, возвращает Enumerator.
static VALUE
enum_each_entry(int argc, VALUE *argv, VALUE obj)
{
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
rb_block_call(obj, id_each, argc, argv, each_val_i, 0);
return obj;
} Вызывает заданный блок для каждого элемента, преобразуя несколько значений, переданных через yield, в массив; возвращает self:
a = []
(1..4).each_entry {|element| a.push(element) } # => 1..4
a # => [1, 2, 3, 4]
a = []
h = {foo: 0, bar: 1, baz:2}
h.each_entry {|element| a.push(element) }
# => {:foo=>0, :bar=>1, :baz=>2}
a # => [[:foo, 0], [:bar, 1], [:baz, 2]]
class Foo
include Enumerable
def each
yield 1
yield 1, 2
yield
end
end
Foo.new.each_entry {|yielded| p yielded }
Результат:
1 [1, 2] nil
Если блок не задан, возвращает Enumerator.
static VALUE
enum_each_slice(VALUE obj, VALUE n)
{
long size = NUM2LONG(n);
VALUE ary;
struct MEMO *memo;
int arity;
if (size <= 0) rb_raise(rb_eArgError, "invalid slice size");
RETURN_SIZED_ENUMERATOR(obj, 1, &n, enum_each_slice_size);
size = limit_by_enum_size(obj, size);
ary = rb_ary_new2(size);
arity = rb_block_arity();
memo = MEMO_NEW(ary, dont_recycle_block_arg(arity), size);
rb_block_call(obj, id_each, 0, 0, each_slice_i, (VALUE)memo);
ary = memo->v1;
if (RARRAY_LEN(ary) > 0) rb_yield(ary);
return obj;
} Вызывает блок для каждого следующего неперекрывающегося кортежа из n элементов; возвращает self:
a = []
(1..10).each_slice(3) {|tuple| a.push(tuple) }
a # => [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
a = []
h = {foo: 0, bar: 1, baz: 2, bat: 3, bam: 4}
h.each_slice(2) {|tuple| a.push(tuple) }
a # => [[[:foo, 0], [:bar, 1]], [[:baz, 2], [:bat, 3]], [[:bam, 4]]]
Если блок не задан, возвращает Enumerator.
static VALUE
enum_each_with_index(int argc, VALUE *argv, VALUE obj)
{
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
rb_block_call(obj, id_each, argc, argv, each_with_index_i, INT2FIX(0));
return obj;
} Вызывает self.each с *args. Если задан блок, он получает каждый элемент и его индекс; возвращает self:
h = {}
(1..4).each_with_index {|element, i| h[element] = i } # => 1..4
h # => {1=>0, 2=>1, 3=>2, 4=>3}
h = {}
%w[a b c d].each_with_index {|element, i| h[element] = i }
# => ["a", "b", "c", "d"]
h # => {"a"=>0, "b"=>1, "c"=>2, "d"=>3}
a = []
h = {foo: 0, bar: 1, baz: 2}
h.each_with_index {|element, i| a.push([i, element]) }
# => {:foo=>0, :bar=>1, :baz=>2}
a # => [[0, [:foo, 0]], [1, [:bar, 1]], [2, [:baz, 2]]]
Если блок не задан, возвращает Enumerator.
static VALUE
enum_each_with_object(VALUE obj, VALUE memo)
{
RETURN_SIZED_ENUMERATOR(obj, 1, &memo, enum_size);
rb_block_call(obj, id_each, 0, 0, each_with_object_i, memo);
return memo;
} Вызывает блок один раз для каждого элемента, передавая ему и элемент, и заданный объект:
(1..4).each_with_object([]) {|i, a| a.push(i**2) }
# => [1, 4, 9, 16]
{foo: 0, bar: 1, baz: 2}.each_with_object({}) {|(k, v), h| h[v] = k }
# => {0=>:foo, 1=>:bar, 2=>:baz}
Если блок не задан, возвращает Enumerator.
static VALUE
enum_filter_map(VALUE obj)
{
VALUE ary;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, filter_map_i, ary);
return ary;
} Возвращает массив, содержащий истинные значения, возвращённые блоком.
Если задан блок, вызывает его для последовательных элементов; возвращает массив, содержащий каждое истинное значение, возвращённое блоком:
(0..9).filter_map {|i| i * 2 if i.even? } # => [0, 4, 8, 12, 16]
{foo: 0, bar: 1, baz: 2}.filter_map {|key, value| key if value.even? } # => [:foo, :baz]
Если блок не задан, возвращает Enumerator.
static VALUE
enum_find(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo;
VALUE if_none;
if_none = rb_check_arity(argc, 0, 1) ? argv[0] : Qnil;
RETURN_ENUMERATOR(obj, argc, argv);
memo = MEMO_NEW(Qundef, 0, 0);
if (rb_block_pair_yield_optimizable())
rb_block_call2(obj, id_each, 0, 0, find_i_fast, (VALUE)memo, RB_BLOCK_NO_USE_PACKED_ARGS);
else
rb_block_call2(obj, id_each, 0, 0, find_i, (VALUE)memo, RB_BLOCK_NO_USE_PACKED_ARGS);
if (memo->u3.cnt) {
return memo->v1;
}
if (!NIL_P(if_none)) {
return rb_funcallv(if_none, id_call, 0, 0);
}
return Qnil;
} Возвращает первый элемент, для которого блок возвращает истинное значение.
Если задан блок, вызывает его для последовательных элементов коллекции; возвращает первый элемент, для которого блок возвращает истинное значение:
(0..9).find {|element| element > 2} # => 3
Если такой элемент не найден, вызывает if_none_proc и возвращает результат его вызова.
(0..9).find(proc {false}) {|element| element > 12} # => false
{foo: 0, bar: 1, baz: 2}.find {|key, value| key.start_with?('b') } # => [:bar, 1]
{foo: 0, bar: 1, baz: 2}.find(proc {[]}) {|key, value| key.start_with?('c') } # => []
Если блок не задан, возвращает Enumerator.
static VALUE
enum_find_all(VALUE obj)
{
VALUE ary;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, find_all_i, ary);
return ary;
} static VALUE
enum_find_index(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo; /* [return value, current index, ] */
VALUE condition_value = Qnil;
rb_block_call_func *func;
if (argc == 0) {
RETURN_ENUMERATOR(obj, 0, 0);
func = find_index_iter_i;
}
else {
rb_scan_args(argc, argv, "1", &condition_value);
if (rb_block_given_p()) {
rb_warn("given block not used");
}
func = find_index_i;
}
memo = MEMO_NEW(Qnil, condition_value, 0);
rb_block_call(obj, id_each, 0, 0, func, (VALUE)memo);
return memo->v1;
} Возвращает индекс первого элемента, соответствующего заданному критерию, или nil, если такой элемент не найден.
Если указан аргумент object, возвращает индекс первого элемента, который == object:
['a', 'b', 'c', 'b'].find_index('b') # => 1
Если задан блок, вызывает его для последовательных элементов; возвращает первый элемент, для которого блок возвращает истинное значение:
['a', 'b', 'c', 'b'].find_index {|element| element.start_with?('b') } # => 1
{foo: 0, bar: 1, baz: 2}.find_index {|key, value| value > 1 } # => 2
Если аргумент и блок не заданы, возвращает Enumerator.
static VALUE
enum_first(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo;
rb_check_arity(argc, 0, 1);
if (argc > 0) {
return enum_take(obj, argv[0]);
}
else {
memo = MEMO_NEW(Qnil, 0, 0);
rb_block_call(obj, id_each, 0, 0, first_i, (VALUE)memo);
return memo->v1;
}
} Возвращает первый элемент или первые элементы.
Если аргумент не указан, возвращает первый элемент или nil, если элементов нет:
(1..4).first # => 1
%w[a b c].first # => "a"
{foo: 1, bar: 1, baz: 2}.first # => [:foo, 1]
[].first # => nil
Если указан целочисленный аргумент n, возвращает массив, содержащий первые n существующих элементов:
(1..4).first(2) # => [1, 2]
%w[a b c d].first(3) # => ["a", "b", "c"]
%w[a b c d].first(50) # => ["a", "b", "c", "d"]
{foo: 1, bar: 1, baz: 2}.first(2) # => [[:foo, 1], [:bar, 1]]
[].first(2) # => []
static VALUE
enum_flat_map(VALUE obj)
{
VALUE ary;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, flat_map_i, ary);
return ary;
} Возвращает массив, содержащий уплощённые объекты, возвращённые блоком.
Если задан блок, вызывает его для последовательных элементов; возвращает уплощённый массив объектов, возвращённых блоком:
[0, 1, 2, 3].flat_map {|element| -element } # => [0, -1, -2, -3]
[0, 1, 2, 3].flat_map {|element| [element, -element] } # => [0, 0, 1, -1, 2, -2, 3, -3]
[[0, 1], [2, 3]].flat_map {|e| e + [100] } # => [0, 1, 100, 2, 3, 100]
{foo: 0, bar: 1, baz: 2}.flat_map {|key, value| [key, value] } # => [:foo, 0, :bar, 1, :baz, 2]
Если блок не задан, возвращает Enumerator.
Псевдоним: collect_concat.
static VALUE
enum_grep(VALUE obj, VALUE pat)
{
return enum_grep0(obj, pat, Qtrue);
} Возвращает массив объектов на основе элементов self, соответствующих заданному шаблону.
Если блок не задан, возвращает массив, содержащий каждый элемент, для которого pattern === element является true:
a = ['foo', 'bar', 'car', 'moo'] a.grep(/ar/) # => ["bar", "car"] (1..10).grep(3..8) # => [3, 4, 5, 6, 7, 8] ['a', 'b', 0, 1].grep(Integer) # => [0, 1]
Если задан блок, вызывает его для каждого совпавшего элемента и возвращает массив, содержащий каждый объект, возвращённый блоком:
a = ['foo', 'bar', 'car', 'moo']
a.grep(/ar/) {|element| element.upcase } # => ["BAR", "CAR"]
См. также: grep_v.
static VALUE
enum_grep_v(VALUE obj, VALUE pat)
{
return enum_grep0(obj, pat, Qfalse);
} Возвращает массив объектов на основе элементов self, которые не соответствуют заданному шаблону.
Если блок не задан, возвращает массив, содержащий каждый элемент, для которого pattern === element является false:
a = ['foo', 'bar', 'car', 'moo'] a.grep_v(/ar/) # => ["foo", "moo"] (1..10).grep_v(3..8) # => [1, 2, 9, 10] ['a', 'b', 0, 1].grep_v(Integer) # => ["a", "b"]
Если задан блок, вызывает его для каждого несовпавшего элемента и возвращает массив, содержащий каждый объект, возвращённый блоком:
a = ['foo', 'bar', 'car', 'moo']
a.grep_v(/ar/) {|element| element.upcase } # => ["FOO", "MOO"]
См. также: grep.
static VALUE
enum_group_by(VALUE obj)
{
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
return enum_hashify(obj, 0, 0, group_by_i);
} Если задан блок, возвращает хеш:
-
Каждый ключ — это значение, возвращённое блоком.
-
Каждое значение — это массив элементов, для которых блок вернул соответствующий ключ.
Примеры:
g = (1..6).group_by {|i| i%3 }
g # => {1=>[1, 4], 2=>[2, 5], 0=>[3, 6]}
h = {foo: 0, bar: 1, baz: 0, bat: 1}
g = h.group_by {|key, value| value }
g # => {0=>[[:foo, 0], [:baz, 0]], 1=>[[:bar, 1], [:bat, 1]]}
Если блок не задан, возвращает Enumerator.
Возвращает, существует ли элемент, для которого object == element:
(1..4).include?(2) # => true
(1..4).include?(5) # => false
(1..4).include?('2') # => false
%w[a b c d].include?('b') # => true
%w[a b c d].include?('2') # => false
{foo: 0, bar: 1, baz: 2}.include?(:foo) # => true
{foo: 0, bar: 1, baz: 2}.include?('foo') # => false
{foo: 0, bar: 1, baz: 2}.include?(0) # => false
static VALUE
enum_inject(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo;
VALUE init, op;
rb_block_call_func *iter = inject_i;
ID id;
int num_args;
if (rb_block_given_p()) {
num_args = rb_scan_args(argc, argv, "02", &init, &op);
}
else {
num_args = rb_scan_args(argc, argv, "11", &init, &op);
}
switch (num_args) {
case 0:
init = Qundef;
break;
case 1:
if (rb_block_given_p()) {
break;
}
id = rb_check_id(&init);
op = id ? ID2SYM(id) : init;
init = Qundef;
iter = inject_op_i;
break;
case 2:
if (rb_block_given_p()) {
rb_warning("given block not used");
}
id = rb_check_id(&op);
if (id) op = ID2SYM(id);
iter = inject_op_i;
break;
}
if (iter == inject_op_i &&
SYMBOL_P(op) &&
RB_TYPE_P(obj, T_ARRAY) &&
rb_method_basic_definition_p(CLASS_OF(obj), id_each)) {
return ary_inject_op(obj, init, op);
}
memo = MEMO_NEW(init, Qnil, op);
rb_block_call(obj, id_each, 0, 0, iter, (VALUE)memo);
if (UNDEF_P(memo->v1)) return Qnil;
return memo->v1;
} Возвращает результат применения редуктора к начальному значению и первому элементу Enumerable. Затем берёт полученный результат и применяет к нему функцию и второй элемент коллекции, и так далее. Возвращаемое значение — результат последнего вызова функции.
Можно считать, что
[ a, b, c, d ].inject(i) { |r, v| fn(r, v) }
эквивалентно
fn(fn(fn(fn(i, a), b), c), d)
В некотором смысле функция inject вставляет функцию между элементами перечислимого объекта.
inject — это псевдоним reduce. Этот метод используется, когда нужно свести коллекцию к одному значению.
Последовательности вызовов
Начнём с наиболее подробного варианта:
enum.inject(initial_value) do |result, next_value| # do something with +result+ and +next_value+ # the value returned by the block becomes the # value passed in to the next iteration # as +result+ end
Например:
product = [ 2, 3, 4 ].inject(1) do |result, next_value| result * next_value end product #=> 24
При выполнении блока сначала он вызывается с 1 (начальным значением) и 2 (первым элементом массива). Блок возвращает 1*2, поэтому на следующей итерации блок вызывается с 2 (предыдущим результатом) и 3. Блок возвращает 6, после чего вызывается в последний раз с 6 и 4. Результат блока, 24, становится значением, возвращаемым inject. Этот код возвращает произведение элементов перечислимого объекта.
Первое упрощение: начальное значение по умолчанию
В предыдущем примере начальное значение 1 на самом деле не требовалось: вычисление произведения списка чисел не зависит от него.
В таких случаях параметр initial_value можно опустить. Тогда inject сначала вызовет блок, передав ему первый элемент коллекции в качестве параметра result, а второй элемент — в качестве next_value.
[ 2, 3, 4 ].inject do |result, next_value| result * next_value end
Это упрощение удобно, но его можно использовать только тогда, когда блок возвращает результат, который можно передать ему в качестве первого параметра.
Вот пример, в котором это условие не выполняется: он возвращает хеш, где ключами являются слова, а значениями — количество вхождений каждого слова в перечислимом объекте.
freqs = File.read("README.md")
.scan(/\w{2,}/)
.reduce(Hash.new(0)) do |counts, word|
counts[word] += 1
counts
end
freqs #=> {"Actions"=>4,
"Status"=>5,
"MinGW"=>3,
"https"=>27,
"github"=>10,
"com"=>15, ... Обратите внимание, что последняя строка блока — это просто слово counts. Это гарантирует, что возвращаемое значение блока будет вычисляемым результатом.
Второе упрощение: функция-редуктор
Функция-редуктор — это функция, которая принимает частичный результат и следующее значение и возвращает следующий частичный результат. Блок, переданный в inject, является редуктором.
Редуктор также можно записать в виде функции и передать в inject её имя (в виде символа). Однако для этого функция
-
должна быть определена для типа результирующего значения;
-
должна принимать один параметр — следующее значение коллекции;
-
должна возвращать обновлённый результат, который также реализует эту функцию.
Вот пример добавления элементов в строку. Оба вызова вызывают функции String#concat и String#+ для текущего результата, передавая им следующее значение.
s = [ "cat", " ", "dog" ].inject("", :concat)
s #=> "cat dog"
s = [ "cat", " ", "dog" ].inject("The result is:", :+)
s #=> "The result is: cat dog"
Вот более сложный пример, в котором объект результата хранит состояние типа, отличного от типа элементов перечислимого объекта.
class Turtle
def initialize
@x = @y = 0
end
def move(dir)
case dir
when "n" then @y += 1
when "s" then @y -= 1
when "e" then @x += 1
when "w" then @x -= 1
end
self
end
end
position = "nnneesw".chars.reduce(Turtle.new, :move)
position #=>> #<Turtle:0x00000001052f4698 @y=2, @x=1>
Третье упрощение: редуктор без начального значения
Если редуктор возвращает значение, которое он может принимать в качестве параметра, начальное значение передавать не нужно. Здесь :* — имя функции times:
product = [ 2, 3, 4 ].inject(:*) product # => 24
И снова конкатенация String:
s = [ "cat", " ", "dog" ].inject(:+) s #=> "cat dog"
А вот пример преобразования хеша в массив двухэлементных подмассивов.
nested = {foo: 0, bar: 1}.inject([], :push)
nested # => [[:foo, 0], [:bar, 1]]
static VALUE
enumerable_lazy(VALUE obj)
{
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size, rb_keyword_given_p());
/* Qfalse indicates that the Enumerator::Lazy has no method name */
rb_ivar_set(result, id_method, Qfalse);
return result;
} Возвращает Enumerator::Lazy, переопределяющий большинство методов Enumerable, чтобы откладывать перечисление и перечислять значения только по мере необходимости.
Пример
Следующая программа находит пифагоровы тройки:
def pythagorean_triples
(1..Float::INFINITY).lazy.flat_map {|z|
(1..z).flat_map {|x|
(x..z).select {|y|
x**2 + y**2 == z**2
}.map {|y|
[x, y, z]
}
}
}
end
# show first ten pythagorean triples
p pythagorean_triples.take(10).force # take is lazy, so force is needed
p pythagorean_triples.first(10) # first is eager
# show pythagorean triples less than 100
p pythagorean_triples.take_while { |*, z| z < 100 }.force
Возвращает массив объектов, возвращённых блоком.
Если блок задан, вызывает его для последовательно передаваемых элементов и возвращает массив объектов, возвращённых блоком:
(0..4).map {|i| i*i } # => [0, 1, 4, 9, 16]
{foo: 0, bar: 1, baz: 2}.map {|key, value| value*2} # => [0, 2, 4]
Если блок не задан, возвращает Enumerator.
static VALUE
enum_max(int argc, VALUE *argv, VALUE obj)
{
VALUE memo;
struct max_t *m = NEW_MEMO_FOR(struct max_t, memo);
VALUE result;
VALUE num;
if (rb_check_arity(argc, 0, 1) && !NIL_P(num = argv[0]))
return rb_nmin_run(obj, num, 0, 1, 0);
m->max = Qundef;
if (rb_block_given_p()) {
rb_block_call(obj, id_each, 0, 0, max_ii, (VALUE)memo);
}
else {
rb_block_call(obj, id_each, 0, 0, max_i, (VALUE)memo);
}
result = m->max;
if (UNDEF_P(result)) return Qnil;
return result;
} Возвращает элемент с максимальным значением согласно заданному критерию. Порядок равных элементов не определён и может быть нестабильным.
Если аргумент и блок не заданы, возвращает максимальный элемент, используя для сравнения собственный метод элементов <=>:
(1..4).max # => 4
(-4..-1).max # => -1
%w[d c b a].max # => "d"
{foo: 0, bar: 1, baz: 2}.max # => [:foo, 0]
[].max # => nil
Если задан положительный целочисленный аргумент n, а блок не задан, возвращает массив, содержащий первые n максимальных элементов, если они есть:
(1..4).max(2) # => [4, 3]
(-4..-1).max(2) # => [-1, -2]
%w[d c b a].max(2) # => ["d", "c"]
{foo: 0, bar: 1, baz: 2}.max(2) # => [[:foo, 0], [:baz, 2]]
[].max(2) # => []
Если задан блок, он определяет максимальные элементы. Блок вызывается с двумя элементами a и b и должен возвращать:
-
отрицательное целое число, если
a < b; -
ноль, если
a == b; -
положительное целое число, если
a > b.
Если задан блок, но аргумент не задан, возвращает максимальный элемент, определённый блоком:
%w[xxx x xxxx xx].max {|a, b| a.size <=> b.size } # => "xxxx"
h = {foo: 0, bar: 1, baz: 2}
h.max {|pair1, pair2| pair1[1] <=> pair2[1] } # => [:baz, 2]
[].max {|a, b| a <=> b } # => nil
Если задан блок и положительный целочисленный аргумент n, возвращает массив, содержащий первые n максимальных элементов, определённых блоком, если они есть.
%w[xxx x xxxx xx].max(2) {|a, b| a.size <=> b.size } # => ["xxxx", "xxx"]
h = {foo: 0, bar: 1, baz: 2}
h.max(2) {|pair1, pair2| pair1[1] <=> pair2[1] }
# => [[:baz, 2], [:bar, 1]]
[].max(2) {|a, b| a <=> b } # => []
static VALUE
enum_max_by(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo;
VALUE num;
rb_check_arity(argc, 0, 1);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
if (argc && !NIL_P(num = argv[0]))
return rb_nmin_run(obj, num, 1, 1, 0);
memo = MEMO_NEW(Qundef, Qnil, 0);
rb_block_call(obj, id_each, 0, 0, max_by_i, (VALUE)memo);
return memo->v2;
} Возвращает элементы, для которых блок возвращает максимальные значения.
Если задан блок, но аргумент не задан, возвращает элемент, для которого блок возвращает максимальное значение:
(1..4).max_by {|element| -element } # => 1
%w[a b c d].max_by {|element| -element.ord } # => "a"
{foo: 0, bar: 1, baz: 2}.max_by {|key, value| -value } # => [:foo, 0]
[].max_by {|element| -element } # => nil
Если задан блок и положительный целочисленный аргумент n, возвращает массив, содержащий n элементов, для которых блок возвращает максимальные значения:
(1..4).max_by(2) {|element| -element }
# => [1, 2]
%w[a b c d].max_by(2) {|element| -element.ord }
# => ["a", "b"]
{foo: 0, bar: 1, baz: 2}.max_by(2) {|key, value| -value }
# => [[:foo, 0], [:bar, 1]]
[].max_by(2) {|element| -element }
# => []
Если блок не задан, возвращает Enumerator.
static VALUE
enum_member(VALUE obj, VALUE val)
{
struct MEMO *memo = MEMO_NEW(val, Qfalse, 0);
rb_block_call(obj, id_each, 0, 0, member_i, (VALUE)memo);
return memo->v2;
} static VALUE
enum_min(int argc, VALUE *argv, VALUE obj)
{
VALUE memo;
struct min_t *m = NEW_MEMO_FOR(struct min_t, memo);
VALUE result;
VALUE num;
if (rb_check_arity(argc, 0, 1) && !NIL_P(num = argv[0]))
return rb_nmin_run(obj, num, 0, 0, 0);
m->min = Qundef;
if (rb_block_given_p()) {
rb_block_call(obj, id_each, 0, 0, min_ii, memo);
}
else {
rb_block_call(obj, id_each, 0, 0, min_i, memo);
}
result = m->min;
if (UNDEF_P(result)) return Qnil;
return result;
} Возвращает элемент с минимальным значением согласно заданному критерию. Порядок равных элементов не определён и может быть нестабильным.
Если аргумент и блок не заданы, возвращает минимальный элемент, используя для сравнения собственный метод элементов <=>:
(1..4).min # => 1
(-4..-1).min # => -4
%w[d c b a].min # => "a"
{foo: 0, bar: 1, baz: 2}.min # => [:bar, 1]
[].min # => nil
Если задан положительный целочисленный аргумент n, а блок не задан, возвращает массив, содержащий первые n минимальных элементов, если они есть:
(1..4).min(2) # => [1, 2]
(-4..-1).min(2) # => [-4, -3]
%w[d c b a].min(2) # => ["a", "b"]
{foo: 0, bar: 1, baz: 2}.min(2) # => [[:bar, 1], [:baz, 2]]
[].min(2) # => []
Если задан блок, он определяет минимальные элементы. Блок вызывается с двумя элементами a и b и должен возвращать:
-
отрицательное целое число, если
a < b; -
ноль, если
a == b; -
положительное целое число, если
a > b.
Если задан блок, но аргумент не задан, возвращает минимальный элемент, определённый блоком:
%w[xxx x xxxx xx].min {|a, b| a.size <=> b.size } # => "x"
h = {foo: 0, bar: 1, baz: 2}
h.min {|pair1, pair2| pair1[1] <=> pair2[1] } # => [:foo, 0]
[].min {|a, b| a <=> b } # => nil
Если задан блок и положительный целочисленный аргумент n, возвращает массив, содержащий первые n минимальных элементов, определённых блоком, если они есть.
%w[xxx x xxxx xx].min(2) {|a, b| a.size <=> b.size } # => ["x", "xx"]
h = {foo: 0, bar: 1, baz: 2}
h.min(2) {|pair1, pair2| pair1[1] <=> pair2[1] }
# => [[:foo, 0], [:bar, 1]]
[].min(2) {|a, b| a <=> b } # => []
static VALUE
enum_min_by(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo;
VALUE num;
rb_check_arity(argc, 0, 1);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
if (argc && !NIL_P(num = argv[0]))
return rb_nmin_run(obj, num, 1, 0, 0);
memo = MEMO_NEW(Qundef, Qnil, 0);
rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo);
return memo->v2;
} Возвращает элементы, для которых блок возвращает минимальные значения.
Если задан блок, но аргумент не задан, возвращает элемент, для которого блок возвращает минимальное значение:
(1..4).min_by {|element| -element } # => 4
%w[a b c d].min_by {|element| -element.ord } # => "d"
{foo: 0, bar: 1, baz: 2}.min_by {|key, value| -value } # => [:baz, 2]
[].min_by {|element| -element } # => nil
Если задан блок и положительный целочисленный аргумент n, возвращает массив, содержащий n элементов, для которых блок возвращает минимальные значения:
(1..4).min_by(2) {|element| -element }
# => [4, 3]
%w[a b c d].min_by(2) {|element| -element.ord }
# => ["d", "c"]
{foo: 0, bar: 1, baz: 2}.min_by(2) {|key, value| -value }
# => [[:baz, 2], [:bar, 1]]
[].min_by(2) {|element| -element }
# => []
Если блок не задан, возвращает Enumerator.
static VALUE
enum_minmax(VALUE obj)
{
VALUE memo;
struct minmax_t *m = NEW_MEMO_FOR(struct minmax_t, memo);
m->min = Qundef;
m->last = Qundef;
if (rb_block_given_p()) {
rb_block_call(obj, id_each, 0, 0, minmax_ii, memo);
if (!UNDEF_P(m->last))
minmax_ii_update(m->last, m->last, m);
}
else {
rb_block_call(obj, id_each, 0, 0, minmax_i, memo);
if (!UNDEF_P(m->last))
minmax_i_update(m->last, m->last, m);
}
if (!UNDEF_P(m->min)) {
return rb_assoc_new(m->min, m->max);
}
return rb_assoc_new(Qnil, Qnil);
} Возвращает массив из двух элементов, содержащий минимальный и максимальный элементы согласно заданному критерию. Порядок равных элементов не определён и может быть нестабильным.
Если аргумент и блок не заданы, возвращает минимальный и максимальный элементы, используя для сравнения собственный метод элементов <=>:
(1..4).minmax # => [1, 4]
(-4..-1).minmax # => [-4, -1]
%w[d c b a].minmax # => ["a", "d"]
{foo: 0, bar: 1, baz: 2}.minmax # => [[:bar, 1], [:foo, 0]]
[].minmax # => [nil, nil]
Если задан блок, возвращает минимальный и максимальный элементы, определённые блоком:
%w[xxx x xxxx xx].minmax {|a, b| a.size <=> b.size } # => ["x", "xxxx"]
h = {foo: 0, bar: 1, baz: 2}
h.minmax {|pair1, pair2| pair1[1] <=> pair2[1] }
# => [[:foo, 0], [:baz, 2]]
[].minmax {|a, b| a <=> b } # => [nil, nil]
static VALUE
enum_minmax_by(VALUE obj)
{
VALUE memo;
struct minmax_by_t *m = NEW_MEMO_FOR(struct minmax_by_t, memo);
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
m->min_bv = Qundef;
m->max_bv = Qundef;
m->min = Qnil;
m->max = Qnil;
m->last_bv = Qundef;
m->last = Qundef;
rb_block_call(obj, id_each, 0, 0, minmax_by_i, memo);
if (!UNDEF_P(m->last_bv))
minmax_by_i_update(m->last_bv, m->last_bv, m->last, m->last, m);
m = MEMO_FOR(struct minmax_by_t, memo);
return rb_assoc_new(m->min, m->max);
} Возвращает массив из двух элементов, содержащий элементы, для которых блок возвращает минимальное и максимальное значения:
(1..4).minmax_by {|element| -element }
# => [4, 1]
%w[a b c d].minmax_by {|element| -element.ord }
# => ["d", "a"]
{foo: 0, bar: 1, baz: 2}.minmax_by {|key, value| -value }
# => [[:baz, 2], [:foo, 0]]
[].minmax_by {|element| -element }
# => [nil, nil]
Если блок не задан, возвращает Enumerator.
static VALUE
enum_none(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
WARN_UNUSED_BLOCK(argc);
ENUM_BLOCK_CALL(none);
return memo->v1;
} Возвращает, удовлетворяет ли какой-либо элемент заданному критерию; результатом будет отрицательный ответ, если не удовлетворяет ни один.
Если аргумент и блок не заданы, возвращает, является ли ни один элемент истинным:
(1..4).none? # => false
[nil, false].none? # => true
{foo: 0}.none? # => false
{foo: 0, bar: 1}.none? # => false
[].none? # => true
Если задан аргумент pattern, а блок не задан, возвращает, выполняется ли условие pattern === element для всех элементов element:
[nil, false, 1.1].none?(Integer) # => true
%w[bar baz bat bam].none?(/m/) # => false
%w[bar baz bat bam].none?(/foo/) # => true
%w[bar baz bat bam].none?('ba') # => true
{foo: 0, bar: 1, baz: 2}.none?(Hash) # => true
{foo: 0}.none?(Array) # => false
[].none?(Integer) # => true
Если задан блок, возвращает, возвращает ли блок истинное значение для нуля элементов:
(1..4).none? {|element| element < 1 } # => true
(1..4).none? {|element| element < 2 } # => false
{foo: 0, bar: 1, baz: 2}.none? {|key, value| value < 0 } # => true
{foo: 0, bar: 1, baz: 2}.none? {|key, value| value < 1 } # => false
static VALUE
enum_one(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qundef);
VALUE result;
WARN_UNUSED_BLOCK(argc);
ENUM_BLOCK_CALL(one);
result = memo->v1;
if (UNDEF_P(result)) return Qfalse;
return result;
} Возвращает, удовлетворяет ли ровно один элемент заданному критерию.
Если аргумент и блок не заданы, возвращает, является ли истинным ровно один элемент:
(1..1).one? # => true
[1, nil, false].one? # => true
(1..4).one? # => false
{foo: 0}.one? # => true
{foo: 0, bar: 1}.one? # => false
[].one? # => false
Если задан аргумент pattern, а блок не задан, возвращает, выполняется ли условие pattern === element ровно для одного элемента element:
[nil, false, 0].one?(Integer) # => true
[nil, false, 0].one?(Numeric) # => true
[nil, false, 0].one?(Float) # => false
%w[bar baz bat bam].one?(/m/) # => true
%w[bar baz bat bam].one?(/foo/) # => false
%w[bar baz bat bam].one?('ba') # => false
{foo: 0, bar: 1, baz: 2}.one?(Array) # => false
{foo: 0}.one?(Array) # => true
[].one?(Integer) # => false
Если задан блок, возвращает, возвращает ли блок истинное значение ровно для одного элемента:
(1..4).one? {|element| element < 2 } # => true
(1..4).one? {|element| element < 1 } # => false
{foo: 0, bar: 1, baz: 2}.one? {|key, value| value < 1 } # => true
{foo: 0, bar: 1, baz: 2}.one? {|key, value| value < 2 } # => false
static VALUE
enum_partition(VALUE obj)
{
struct MEMO *memo;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
memo = MEMO_NEW(rb_ary_new(), rb_ary_new(), 0);
rb_block_call(obj, id_each, 0, 0, partition_i, (VALUE)memo);
return rb_assoc_new(memo->v1, memo->v2);
} Если задан блок, возвращает массив из двух массивов:
-
первый содержит элементы, для которых блок возвращает истинное значение;
-
второй содержит все остальные элементы.
Примеры:
p = (1..4).partition {|i| i.even? }
p # => [[2, 4], [1, 3]]
p = ('a'..'d').partition {|c| c < 'c' }
p # => [["a", "b"], ["c", "d"]]
h = {foo: 0, bar: 1, baz: 2, bat: 3}
p = h.partition {|key, value| key.start_with?('b') }
p # => [[[:bar, 1], [:baz, 2], [:bat, 3]], [[:foo, 0]]]
p = h.partition {|key, value| value < 2 }
p # => [[[:foo, 0], [:bar, 1]], [[:baz, 2], [:bat, 3]]]
Если блок не задан, возвращает Enumerator.
Связанный метод: Enumerable#group_by.
static VALUE
enum_reject(VALUE obj)
{
VALUE ary;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, reject_i, ary);
return ary;
} Возвращает массив объектов, отклонённых блоком.
Если задан блок, вызывает его для последовательно передаваемых элементов и возвращает массив элементов, для которых блок возвращает nil или false:
(0..9).reject {|i| i * 2 if i.even? } # => [1, 3, 5, 7, 9]
{foo: 0, bar: 1, baz: 2}.reject {|key, value| key if value.odd? } # => {:foo=>0, :baz=>2}
Если блок не задан, возвращает Enumerator.
Связанный метод: select.
static VALUE
enum_reverse_each(int argc, VALUE *argv, VALUE obj)
{
VALUE ary;
long len;
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
ary = enum_to_a(argc, argv, obj);
len = RARRAY_LEN(ary);
while (len--) {
long nlen;
rb_yield(RARRAY_AREF(ary, len));
nlen = RARRAY_LEN(ary);
if (nlen < len) {
len = nlen;
}
}
return obj;
} Если задан блок, вызывает его для каждого элемента в обратном порядке и возвращает self:
a = []
(1..4).reverse_each {|element| a.push(-element) } # => 1..4
a # => [-4, -3, -2, -1]
a = []
%w[a b c d].reverse_each {|element| a.push(element) }
# => ["a", "b", "c", "d"]
a # => ["d", "c", "b", "a"]
a = []
h.reverse_each {|element| a.push(element) }
# => {:foo=>0, :bar=>1, :baz=>2}
a # => [[:baz, 2], [:bar, 1], [:foo, 0]]
Если блок не задан, возвращает Enumerator.
Возвращает массив, содержащий элементы, выбранные блоком.
Если задан блок, вызывает его для последовательно передаваемых элементов и возвращает массив элементов, для которых блок возвращает истинное значение:
(0..9).select {|element| element % 3 == 0 } # => [0, 3, 6, 9]
a = {foo: 0, bar: 1, baz: 2}.select {|key, value| key.start_with?('b') }
a # => {:bar=>1, :baz=>2}
Если блок не задан, возвращает Enumerator.
Связанный метод: reject.
static VALUE
enum_slice_after(int argc, VALUE *argv, VALUE enumerable)
{
VALUE enumerator;
VALUE pat = Qnil, pred = Qnil;
if (rb_block_given_p()) {
if (0 < argc)
rb_raise(rb_eArgError, "both pattern and block are given");
pred = rb_block_proc();
}
else {
rb_scan_args(argc, argv, "1", &pat);
}
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, id_sliceafter_enum, enumerable);
rb_ivar_set(enumerator, id_sliceafter_pat, pat);
rb_ivar_set(enumerator, id_sliceafter_pred, pred);
rb_block_call(enumerator, idInitialize, 0, 0, sliceafter_i, enumerator);
return enumerator;
} Создаёт перечислитель для групп элементов. Концы групп определяются шаблоном и блоком.
Если pattern === elt возвращает true или блок возвращает true для элемента, этот элемент становится концом группы.
Шаблон === и блок применяются к элементам enum от первого до последнего.
Результирующий перечислитель выдаёт сгруппированные элементы в виде массива. Поэтому метод each можно вызывать следующим образом:
enum.slice_after(pattern).each { |ary| ... }
enum.slice_after { |elt| bool }.each { |ary| ... } Также можно использовать другие методы класса Enumerator и модуля Enumerable, например map и другие.
Например, строки продолжения (строки, заканчивающиеся обратной косой чертой) можно объединить следующим образом:
lines = ["foo\n", "bar\\\n", "baz\n", "\n", "qux\n"]
e = lines.slice_after(/(?<!\\)\n\z/)
p e.to_a
#=> [["foo\n"], ["bar\\\n", "baz\n"], ["\n"], ["qux\n"]]
p e.map {|ll| ll[0...-1].map {|l| l.sub(/\\\n\z/, "") }.join + ll.last }
#=>["foo\n", "barbaz\n", "\n", "qux\n"]
static VALUE
enum_slice_before(int argc, VALUE *argv, VALUE enumerable)
{
VALUE enumerator;
if (rb_block_given_p()) {
if (argc != 0)
rb_error_arity(argc, 0, 0);
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, id_slicebefore_sep_pred, rb_block_proc());
}
else {
VALUE sep_pat;
rb_scan_args(argc, argv, "1", &sep_pat);
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, id_slicebefore_sep_pat, sep_pat);
}
rb_ivar_set(enumerator, id_slicebefore_enumerable, enumerable);
rb_block_call(enumerator, idInitialize, 0, 0, slicebefore_i, enumerator);
return enumerator;
} Если задан аргумент pattern, возвращает перечислитель, который использует шаблон для разбиения элементов на массивы («срезы»). Элемент начинает новый срез, если element === pattern (или если это первый элемент).
a = %w[foo bar fop for baz fob fog bam foy]
e = a.slice_before(/ba/) # => #<Enumerator: ...>
e.each {|array| p array }
Результат:
["foo"] ["bar", "fop", "for"] ["baz", "fob", "fog"] ["bam", "foy"]
Если задан блок, возвращает перечислитель, который использует блок для разбиения элементов на массивы. Элемент начинает новый срез, если блок возвращает истинное значение (или если это первый элемент):
e = (1..20).slice_before {|i| i % 4 == 2 } # => #<Enumerator: ...>
e.each {|array| p array }
Результат:
[1] [2, 3, 4, 5] [6, 7, 8, 9] [10, 11, 12, 13] [14, 15, 16, 17] [18, 19, 20]
Также можно использовать другие методы класса Enumerator и модуля Enumerable, например to_a, map и другие.
Например, перебор записей ChangeLog можно реализовать следующим образом:
# iterate over ChangeLog entries.
open("ChangeLog") { |f|
f.slice_before(/\A\S/).each { |e| pp e }
}
# same as above. block is used instead of pattern argument.
open("ChangeLog") { |f|
f.slice_before { |line| /\A\S/ === line }.each { |e| pp e }
}
Команда «svn proplist -R» выводит многострочный результат для каждого файла. Его можно разбить на группы следующим образом:
IO.popen([{"LC_ALL"=>"C"}, "svn", "proplist", "-R"]) { |f|
f.lines.slice_before(/\AProp/).each { |lines| p lines }
}
#=> ["Properties on '.':\n", " svn:ignore\n", " svk:merge\n"]
# ["Properties on 'goruby.c':\n", " svn:eol-style\n"]
# ["Properties on 'complex.c':\n", " svn:mime-type\n", " svn:eol-style\n"]
# ["Properties on 'regparse.c':\n", " svn:eol-style\n"]
# ...
Если блоку необходимо сохранять состояние между несколькими элементами, можно использовать локальные переменные. Например, три и более последовательных возрастающих числа можно объединить следующим образом (см. chunk_while — более подходящий способ):
a = [0, 2, 3, 4, 6, 7, 9]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
es.length <= 2 ? es.join(",") : "#{es.first}-#{es.last}"
}.join(",")
#=> "0,2-4,6,7,9"
Однако локальные переменные следует использовать осторожно, если результирующий перечислитель перебирается два или более раз. Локальные переменные необходимо инициализировать при каждом переборе. Для этого можно использовать Enumerator.new.
# Word wrapping. This assumes all characters have same width.
def wordwrap(words, maxwidth)
Enumerator.new {|y|
# cols is initialized in Enumerator.new.
cols = 0
words.slice_before { |w|
cols += 1 if cols != 0
cols += w.length
if maxwidth < cols
cols = w.length
true
else
false
end
}.each {|ws| y.yield ws }
}
end
text = (1..20).to_a.join(" ")
enum = wordwrap(text.split(/\s+/), 10)
puts "-"*10
enum.each { |ws| puts ws.join(" ") } # first enumeration.
puts "-"*10
enum.each { |ws| puts ws.join(" ") } # second enumeration generates same result as the first.
puts "-"*10
#=> ----------
# 1 2 3 4 5
# 6 7 8 9 10
# 11 12 13
# 14 15 16
# 17 18 19
# 20
# ----------
# 1 2 3 4 5
# 6 7 8 9 10
# 11 12 13
# 14 15 16
# 17 18 19
# 20
# ----------
mbox содержит последовательность писем, каждое из которых начинается со строки Unix From. Поэтому каждое письмо можно извлечь, разбивая перед строкой Unix From.
# parse mbox
open("mbox") { |f|
f.slice_before { |line|
line.start_with? "From "
}.each { |mail|
unix_from = mail.shift
i = mail.index("\n")
header = mail[0...i]
body = mail[(i+1)..-1]
body.pop if body.last == "\n"
fields = header.slice_before { |line| !" \t".include?(line[0]) }.to_a
p unix_from
pp fields
pp body
}
}
# split mails in mbox (slice before Unix From line after an empty line)
open("mbox") { |f|
emp = true
f.slice_before { |line|
prevemp = emp
emp = line == "\n"
prevemp && line.start_with?("From ")
}.each { |mail|
mail.pop if mail.last == "\n"
pp mail
}
}
static VALUE
enum_slice_when(VALUE enumerable)
{
VALUE enumerator;
VALUE pred;
pred = rb_block_proc();
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, id_slicewhen_enum, enumerable);
rb_ivar_set(enumerator, id_slicewhen_pred, pred);
rb_ivar_set(enumerator, id_slicewhen_inverted, Qfalse);
rb_block_call(enumerator, idInitialize, 0, 0, slicewhen_i, enumerator);
return enumerator;
} Создаёт перечислитель для групп элементов. Начала групп определяются блоком.
Этот метод разделяет группы, используя соседние элементы перечислителя-получателя — elt_before и elt_after. Группы разделяются между elt_before и elt_after, если блок возвращает true.
Блок вызывается на единицу меньше количества элементов перечислителя-получателя.
Результирующий перечислитель выдаёт сгруппированные элементы в виде массива. Поэтому метод each можно вызывать следующим образом:
enum.slice_when { |elt_before, elt_after| bool }.each { |ary| ... } Также можно использовать другие методы класса Enumerator и модуля Enumerable, например to_a, map и другие.
Например, возрастающие на единицу подпоследовательности можно разбить на группы следующим образом:
a = [1,2,4,9,10,11,12,15,16,19,20,21]
b = a.slice_when {|i, j| i+1 != j }
p b.to_a #=> [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
p c #=> [[1, 2], [4], "9-12", [15, 16], "19-21"]
d = c.join(",")
p d #=> "1,2,4,9-12,15,16,19-21"
Близкие элементы (порог: 6) в отсортированном массиве можно разбить на группы следующим образом:
a = [3, 11, 14, 25, 28, 29, 29, 41, 55, 57]
p a.slice_when {|i, j| 6 < j - i }.to_a
#=> [[3], [11, 14], [25, 28, 29, 29], [41], [55, 57]]
Возрастающие (неубывающие) подпоследовательности можно разбить на группы следующим образом:
a = [0, 9, 2, 2, 3, 2, 7, 5, 9, 5]
p a.slice_when {|i, j| i > j }.to_a
#=> [[0, 9], [2, 2, 3], [2, 7], [5, 9], [5]]
Соседние чётные и нечётные числа можно разбить на группы следующим образом: (это также можно сделать с помощью Enumerable#chunk.)
a = [7, 5, 9, 2, 0, 7, 9, 4, 2, 0]
p a.slice_when {|i, j| i.even? != j.even? }.to_a
#=> [[7, 5, 9], [2, 0], [7, 9], [4, 2, 0]]
Абзацы (непустые строки с завершающими пустыми строками) можно разбить на группы следующим образом: (Чтобы игнорировать пустые строки, см. Enumerable#chunk.)
lines = ["foo\n", "bar\n", "\n", "baz\n", "qux\n"]
p lines.slice_when {|l1, l2| /\A\s*\z/ =~ l1 && /\S/ =~ l2 }.to_a
#=> [["foo\n", "bar\n", "\n"], ["baz\n", "qux\n"]]
Enumerable#chunk_while работает аналогично, но разделяет элементы, когда блок возвращает false, а не true.
static VALUE
enum_sort(VALUE obj)
{
return rb_ary_sort_bang(enum_to_a(0, 0, obj));
} Возвращает массив, содержащий отсортированные элементы self. Порядок равных элементов не определён и может быть нестабильным.
Если блок не задан, сортировка выполняется с использованием собственного метода элементов <=>:
%w[b c a d].sort # => ["a", "b", "c", "d"]
{foo: 0, bar: 1, baz: 2}.sort # => [[:bar, 1], [:baz, 2], [:foo, 0]]
Если блок задан, порядок определяется сравнениями в блоке. Блок вызывается с двумя элементами a и b и должен возвращать:
-
Отрицательное целое число, если
a < b. -
Ноль, если
a == b. -
Положительное целое число, если
a > b.
Примеры:
a = %w[b c a d]
a.sort {|a, b| b <=> a } # => ["d", "c", "b", "a"]
h = {foo: 0, bar: 1, baz: 2}
h.sort {|a, b| b <=> a } # => [[:foo, 0], [:baz, 2], [:bar, 1]]
См. также sort_by. Этот метод реализует преобразование Шварца, полезное, когда вычисление ключа или сравнение обходится дорого.
static VALUE
enum_sort_by(VALUE obj)
{
VALUE ary, buf;
struct MEMO *memo;
long i;
struct sort_by_data *data;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
if (RB_TYPE_P(obj, T_ARRAY) && RARRAY_LEN(obj) <= LONG_MAX/2) {
ary = rb_ary_new2(RARRAY_LEN(obj)*2);
}
else {
ary = rb_ary_new();
}
RBASIC_CLEAR_CLASS(ary);
buf = rb_ary_hidden_new(SORT_BY_BUFSIZE*2);
rb_ary_store(buf, SORT_BY_BUFSIZE*2-1, Qnil);
memo = MEMO_NEW(0, 0, 0);
data = (struct sort_by_data *)&memo->v1;
RB_OBJ_WRITE(memo, &data->ary, ary);
RB_OBJ_WRITE(memo, &data->buf, buf);
data->n = 0;
data->primitive_uniformed = SORT_BY_UNIFORMED((CMP_OPTIMIZABLE(FLOAT) && CMP_OPTIMIZABLE(INTEGER)),
CMP_OPTIMIZABLE(FLOAT),
CMP_OPTIMIZABLE(INTEGER));
rb_block_call(obj, id_each, 0, 0, sort_by_i, (VALUE)memo);
ary = data->ary;
buf = data->buf;
if (data->n) {
rb_ary_resize(buf, data->n*2);
rb_ary_concat(ary, buf);
}
if (RARRAY_LEN(ary) > 2) {
if (data->primitive_uniformed) {
RARRAY_PTR_USE(ary, ptr,
rb_uniform_intro_sort_2((struct rb_uniform_sort_data*)ptr,
(struct rb_uniform_sort_data*)(ptr + RARRAY_LEN(ary))));
}
else {
RARRAY_PTR_USE(ary, ptr,
ruby_qsort(ptr, RARRAY_LEN(ary)/2, 2*sizeof(VALUE),
sort_by_cmp, (void *)ary));
}
}
if (RBASIC(ary)->klass) {
rb_raise(rb_eRuntimeError, "sort_by reentered");
}
for (i=1; i<RARRAY_LEN(ary); i+=2) {
RARRAY_ASET(ary, i/2, RARRAY_AREF(ary, i));
}
rb_ary_resize(ary, RARRAY_LEN(ary)/2);
RBASIC_SET_CLASS_RAW(ary, rb_cArray);
return ary;
} Если блок задан, возвращает массив элементов self, отсортированных по значению, возвращаемому блоком для каждого элемента. Порядок равных элементов не определён и может быть нестабильным.
Примеры:
a = %w[xx xxx x xxxx]
a.sort_by {|s| s.size } # => ["x", "xx", "xxx", "xxxx"]
a.sort_by {|s| -s.size } # => ["xxxx", "xxx", "xx", "x"]
h = {foo: 2, bar: 1, baz: 0}
h.sort_by{|key, value| value } # => [[:baz, 0], [:bar, 1], [:foo, 2]]
h.sort_by{|key, value| key } # => [[:bar, 1], [:baz, 0], [:foo, 2]]
Если блок не задан, возвращает Enumerator.
Текущая реализация sort_by создаёт массив кортежей, содержащих исходный элемент коллекции и преобразованное значение. Поэтому sort_by довольно затратен, когда наборы ключей просты.
require 'benchmark'
a = (1..100000).map { rand(100000) }
Benchmark.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"]
Чтобы получить обратный порядок для заданной последовательности, можно использовать следующее:
ary.sort_by { ... }.reverse! static VALUE
enum_sum(int argc, VALUE* argv, VALUE obj)
{
struct enum_sum_memo memo;
VALUE beg, end;
int excl;
memo.v = (rb_check_arity(argc, 0, 1) == 0) ? LONG2FIX(0) : argv[0];
memo.block_given = rb_block_given_p();
memo.n = 0;
memo.r = Qundef;
if ((memo.float_value = RB_FLOAT_TYPE_P(memo.v))) {
memo.f = RFLOAT_VALUE(memo.v);
memo.c = 0.0;
}
else {
memo.f = 0.0;
memo.c = 0.0;
}
if (RTEST(rb_range_values(obj, &beg, &end, &excl))) {
if (!memo.block_given && !memo.float_value &&
(FIXNUM_P(beg) || RB_BIGNUM_TYPE_P(beg)) &&
(FIXNUM_P(end) || RB_BIGNUM_TYPE_P(end))) {
return int_range_sum(beg, end, excl, memo.v);
}
}
if (RB_TYPE_P(obj, T_HASH) &&
rb_method_basic_definition_p(CLASS_OF(obj), id_each))
hash_sum(obj, &memo);
else
rb_block_call(obj, id_each, 0, 0, enum_sum_i, (VALUE)&memo);
if (memo.float_value) {
return DBL2NUM(memo.f + memo.c);
}
else {
if (memo.n != 0)
memo.v = rb_fix_plus(LONG2FIX(memo.n), memo.v);
if (!UNDEF_P(memo.r)) {
memo.v = rb_rational_plus(memo.r, memo.v);
}
return memo.v;
}
} Если блок не задан, возвращает сумму initial_value и элементов:
(1..100).sum # => 5050
(1..100).sum(1) # => 5051
('a'..'d').sum('foo') # => "fooabcd"
Как правило, сумма вычисляется с помощью методов + и each; для оптимизации производительности эти методы могут не использоваться, поэтому переопределение этих методов может не повлиять на результат.
Одна из таких оптимизаций: если возможно, вычисление выполняется по формуле суммирования Гаусса n(n+1)/2:
100 * (100 + 1) / 2 # => 5050
Если блок задан, он вызывается для каждого элемента; метод возвращает сумму initial_value и значений, возвращаемых блоком:
(1..4).sum {|i| i*i } # => 30
(1..4).sum(100) {|i| i*i } # => 130
h = {a: 0, b: 1, c: 2, d: 3, e: 4, f: 5}
h.sum {|key, value| value.odd? ? value : 0 } # => 9
('a'..'f').sum('x') {|c| c < 'd' ? c : '' } # => "xabc"
static VALUE
enum_take(VALUE obj, VALUE n)
{
struct MEMO *memo;
VALUE result;
long len = NUM2LONG(n);
if (len < 0) {
rb_raise(rb_eArgError, "attempt to take negative size");
}
if (len == 0) return rb_ary_new2(0);
result = rb_ary_new2(len);
memo = MEMO_NEW(result, 0, len);
rb_block_call(obj, id_each, 0, 0, take_i, (VALUE)memo);
return result;
} Для неотрицательного целого числа n возвращает первые n элементов:
r = (1..4)
r.take(2) # => [1, 2]
r.take(0) # => []
h = {foo: 0, bar: 1, baz: 2, bat: 3}
h.take(2) # => [[:foo, 0], [:bar, 1]]
static VALUE
enum_take_while(VALUE obj)
{
VALUE ary;
RETURN_ENUMERATOR(obj, 0, 0);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, take_while_i, ary);
return ary;
} Вызывает блок с последовательными элементами, пока блок возвращает истинное значение; возвращает массив всех элементов до этого момента:
(1..4).take_while{|i| i < 3 } # => [1, 2]
h = {foo: 0, bar: 1, baz: 2}
h.take_while{|element| key, value = *element; value < 2 }
# => [[:foo, 0], [:bar, 1]]
Если блок не задан, возвращает Enumerator.
static VALUE
enum_tally(int argc, VALUE *argv, VALUE obj)
{
VALUE hash;
if (rb_check_arity(argc, 0, 1)) {
hash = rb_to_hash_type(argv[0]);
rb_check_frozen(hash);
}
else {
hash = rb_hash_new();
}
return enum_hashify_into(obj, 0, 0, tally_i, hash);
} Если аргумент hash не задан, возвращает новый хеш, ключами которого являются уникальные элементы self; каждое целочисленное значение — это количество вхождений соответствующего элемента:
%w[a b c b c a c b].tally # => {"a"=>2, "b"=>3, "c"=>3}
Если аргумент hash задан, возвращает hash, возможно, дополненный; для каждого элемента ele в self:
-
Добавляет его как ключ со значением ноль, если такого ключа ещё нет:
hash[ele] = 0 unless hash.include?(ele)
-
Увеличивает значение ключа
ele:hash[ele] += 1
Это удобно для накопления подсчётов по нескольким перечисляемым объектам:
h = {} # => {}
%w[a c d b c a].tally(h) # => {"a"=>2, "c"=>2, "d"=>1, "b"=>1}
%w[b a z].tally(h) # => {"a"=>3, "c"=>2, "d"=>1, "b"=>2, "z"=>1}
%w[b a m].tally(h) # => {"a"=>4, "c"=>2, "d"=>1, "b"=>3, "z"=>1, "m"=>1}
Ключ, который добавляется или ищется для элемента, зависит от класса self; см. раздел Enumerable в классах Ruby.
Примеры:
-
Array(и некоторые классы, подобные массивам): ключом является элемент (как указано выше). -
Hash(и некоторые классы, подобные хешам): ключом является массив из двух элементов, образованный парой ключ-значение:h = {} # => {} {foo: 'a', bar: 'b'}.tally(h) # => {[:foo, "a"]=>1, [:bar, "b"]=>1} {foo: 'c', bar: 'd'}.tally(h) # => {[:foo, "a"]=>1, [:bar, "b"]=>1, [:foo, "c"]=>1, [:bar, "d"]=>1} {foo: 'a', bar: 'b'}.tally(h) # => {[:foo, "a"]=>2, [:bar, "b"]=>2, [:foo, "c"]=>1, [:bar, "d"]=>1} {foo: 'c', bar: 'd'}.tally(h) # => {[:foo, "a"]=>2, [:bar, "b"]=>2, [:foo, "c"]=>2, [:bar, "d"]=>2}
static VALUE
enum_to_a(int argc, VALUE *argv, VALUE obj)
{
VALUE ary = rb_ary_new();
rb_block_call_kw(obj, id_each, argc, argv, collect_all, ary, RB_PASS_CALLED_KEYWORDS);
return ary;
} Возвращает массив, содержащий элементы self:
(0..4).to_a # => [0, 1, 2, 3, 4]
static VALUE
enum_to_h(int argc, VALUE *argv, VALUE obj)
{
rb_block_call_func *iter = rb_block_given_p() ? enum_to_h_ii : enum_to_h_i;
return enum_hashify(obj, argc, argv, iter);
} Если self состоит из массивов из двух элементов, возвращает хеш, каждая запись которого представляет собой пару ключ-значение, образованную одним из этих массивов:
[[:foo, 0], [:bar, 1], [:baz, 2]].to_h # => {:foo=>0, :bar=>1, :baz=>2}
Если задан блок, он вызывается для каждого элемента self; блок должен возвращать массив из двух элементов, который становится парой ключ-значение в возвращаемом хеше:
(0..3).to_h {|i| [i, i ** 2]} # => {0=>0, 1=>1, 2=>4, 3=>9}
Вызывает исключение, если элемент self не является массивом из двух элементов и блок не передан.
# File prelude.rb, line 34
def to_set(*args, &block)
klass = if args.empty?
Set
else
warn "passing arguments to Enumerable#to_set is deprecated", uplevel: 1
args.shift
end
klass.new(self, *args, &block)
end Создаёт множество из перечисляемого объекта с заданными аргументами. Передача аргументов этому методу устарела.
static VALUE
enum_uniq(VALUE obj)
{
VALUE hash, ret;
rb_block_call_func *const func =
rb_block_given_p() ? uniq_iter : uniq_func;
hash = rb_obj_hide(rb_hash_new());
rb_block_call(obj, id_each, 0, 0, func, hash);
ret = rb_hash_values(hash);
rb_hash_clear(hash);
return ret;
} Если блок не задан, возвращает новый массив, содержащий только уникальные элементы; в массиве нет двух элементов e0 и e1, для которых выполняется e0.eql?(e1):
%w[a b c c b a a b c].uniq # => ["a", "b", "c"] [0, 1, 2, 2, 1, 0, 0, 1, 2].uniq # => [0, 1, 2]
Если блок задан, возвращает новый массив, содержащий только элементы, для которых блок возвращает уникальное значение:
a = [0, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1]
a.uniq {|i| i.even? ? i : 0 } # => [0, 2, 4]
a = %w[a b c d e e d c b a a b c d e]
a.uniq {|c| c < 'c' } # => ["a", "c"]
static VALUE
enum_zip(int argc, VALUE *argv, VALUE obj)
{
int i;
ID conv;
struct MEMO *memo;
VALUE result = Qnil;
VALUE args = rb_ary_new4(argc, argv);
int allary = TRUE;
argv = RARRAY_PTR(args);
for (i=0; i<argc; i++) {
VALUE ary = rb_check_array_type(argv[i]);
if (NIL_P(ary)) {
allary = FALSE;
break;
}
argv[i] = ary;
}
if (!allary) {
static const VALUE sym_each = STATIC_ID2SYM(id_each);
CONST_ID(conv, "to_enum");
for (i=0; i<argc; i++) {
if (!rb_respond_to(argv[i], id_each)) {
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE" (must respond to :each)",
rb_obj_class(argv[i]));
}
argv[i] = rb_funcallv(argv[i], conv, 1, &sym_each);
}
}
if (!rb_block_given_p()) {
result = rb_ary_new();
}
/* TODO: use NODE_DOT2 as memo(v, v, -) */
memo = MEMO_NEW(result, args, 0);
rb_block_call(obj, id_each, 0, 0, allary ? zip_ary : zip_i, (VALUE)memo);
return result;
} Если блок не задан, возвращает новый массив new_array размером self.size, элементы которого — массивы. Каждый вложенный массив new_array[n] имеет размер other_enums.size+1 и содержит:
-
Элемент self с индексом
n. -
Элемент с индексом
nиз каждого объектаother_enums.
Если все other_enums и self имеют одинаковый размер, результат включает все элементы и дополнение значением nil не выполняется:
a = [:a0, :a1, :a2, :a3]
b = [:b0, :b1, :b2, :b3]
c = [:c0, :c1, :c2, :c3]
d = a.zip(b, c)
d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, :c2], [:a3, :b3, :c3]]
f = {foo: 0, bar: 1, baz: 2}
g = {goo: 3, gar: 4, gaz: 5}
h = {hoo: 6, har: 7, haz: 8}
d = f.zip(g, h)
d # => [
# [[:foo, 0], [:goo, 3], [:hoo, 6]],
# [[:bar, 1], [:gar, 4], [:har, 7]],
# [[:baz, 2], [:gaz, 5], [:haz, 8]]
# ]
Если размер какого-либо перечисляемого объекта в other_enums меньше размера self, он дополняется до self.size значением nil:
a = [:a0, :a1, :a2, :a3] b = [:b0, :b1, :b2] c = [:c0, :c1] d = a.zip(b, c) d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, nil], [:a3, nil, nil]]
Если размер какого-либо перечисляемого объекта в other_enums больше размера self, его конечные элементы игнорируются:
a = [:a0, :a1, :a2, :a3] b = [:b0, :b1, :b2, :b3, :b4] c = [:c0, :c1, :c2, :c3, :c4, :c5] d = a.zip(b, c) d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, :c2], [:a3, :b3, :c3]]
Если блок задан, он вызывается с каждым подмассивом (сформированным, как описано выше); метод возвращает nil:
a = [:a0, :a1, :a2, :a3]
b = [:b0, :b1, :b2, :b3]
c = [:c0, :c1, :c2, :c3]
a.zip(b, c) {|sub_array| p sub_array} # => nil
Вывод:
[:a0, :b0, :c0] [:a1, :b1, :c1] [:a2, :b2, :c2] [:a3, :b3, :c3]
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.