Spec-Zone.ru › Ruby 4.0

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:

  • ARGF

  • Array

  • Dir

  • Enumerator

  • ENV (расширяет)

  • Hash

  • IO

  • Range

  • Struct

Следующие классы стандартной библиотеки 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 возвращает количество байтов в файле, а не количество строк.

Открытые методы экземпляра

all? → true or false Показать исходный код
all?(pattern) → true or false
all? {|element| ... } → true or false
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

См. также: any?, none? one?.

any? → true or false Показать исходный код
any?(pattern) → true or false
any? {|element| ... } → true or 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

См. также: all?, none?, one?.

chain(*enums) → enumerator Показать исходный код
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]
chunk {|array| ... } → enumerator Показать исходный код
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
}
chunk_while {|elt_before, elt_after| bool } → an_enumerator Показать исходный код
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.

collect Показать исходный код
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;
}
Также имеет псевдоним: map
collect_concat
Псевдоним метода: flat_map
compact → array Показать исходный код
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]
count → integer Показать исходный код
count(object) → integer
count {|element| ... } → integer
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
cycle(n = nil) {|element| ...} → nil Показать исходный код
cycle(n = nil) → enumerator
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.

detect
Псевдоним метода: find
drop(n) → array Показать исходный код
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]]
drop_while {|element| ... } → array Показать исходный код
drop_while → enumerator
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
each_cons(n) { ... } → self Показать исходный код
each_cons(n) → enumerator
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.

each_entry(*args) {|element| ... } → self Показать исходный код
each_entry(*args) → 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.

each_slice(n) { ... } → self Показать исходный код
each_slice(n) → 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.

each_with_index(*args) {|element, i| ..... } → self Показать исходный код
each_with_index(*args) → 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.

each_with_object(object) { |(*args), memo_object| ... } → object Показать исходный код
each_with_object(object) → 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.

entries
Псевдоним метода: to_a
filter
Псевдоним метода: find_all
filter_map {|element| ... } → array Показать исходный код
filter_map → 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.

find(if_none_proc = nil) {|element| ... } → object or nil Показать исходный код
find(if_none_proc = nil) → 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.

Также имеет псевдоним: detect
find_all Показать исходный код
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;
}
Также имеет псевдонимы: select, filter
find_index(object) → integer or nil Показать исходный код
find_index {|element| ... } → integer or nil
find_index → enumerator
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.

first → element or nil Показать исходный код
first(n) → array
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)                       # => []
flat_map {|element| ... } → array Показать исходный код
flat_map → enumerator
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.

Также имеет псевдоним: collect_concat
grep(pattern) → array Показать исходный код
grep(pattern) {|element| ... } → array
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.

grep_v(pattern) → array Показать исходный код
grep_v(pattern) {|element| ... } → array
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.

group_by {|element| ... } → hash Показать исходный код
group_by → enumerator
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.

include?(object) → true or false

Возвращает, существует ли элемент, для которого 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
Псевдоним метода: member?
inject(symbol) → object Показать исходный код
inject(initial_value, symbol) → object
inject {|memo, value| ... } → object
inject(initial_value) {|memo, value| ... } → object
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 её имя (в виде символа). Однако для этого функция

  1. должна быть определена для типа результирующего значения;

  2. должна принимать один параметр — следующее значение коллекции;

  3. должна возвращать обновлённый результат, который также реализует эту функцию.

Вот пример добавления элементов в строку. Оба вызова вызывают функции 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]]
Также имеет псевдоним: reduce
lazy → lazy_enumerator Показать исходный код
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
map {|element| ... } → array
map → enumerator

Возвращает массив объектов, возвращённых блоком.

Если блок задан, вызывает его для последовательно передаваемых элементов и возвращает массив объектов, возвращённых блоком:

(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.

Псевдоним для: collect
max → element Показать исходный код
max(n) → array
max {|a, b| ... } → element
max(n) {|a, b| ... } → array
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 }                          # => []

Связанные методы: min, minmax, max_by.

max_by {|element| ... } → element Показать исходный код
max_by(n) {|element| ... } → array
max_by → enumerator
max_by(n) → enumerator
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.

Связанные методы: max, minmax, min_by.

member? Показать исходный код
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;
}
Также имеет псевдоним: include?
min → element Показать исходный код
min(n) → array
min {|a, b| ... } → element
min(n) {|a, b| ... } → array
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 }                          # => []

Связанные методы: min_by, minmax, max.

min_by {|element| ... } → element Показать исходный код
min_by(n) {|element| ... } → array
min_by → enumerator
min_by(n) → enumerator
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.

Связанные методы: min, minmax, max_by.

minmax → [minimum, maximum] Показать исходный код
minmax {|a, b| ... } → [minimum, maximum]
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]

Связанные методы: min, max, minmax_by.

minmax_by {|element| ... } → [minimum, maximum] Показать исходный код
minmax_by → enumerator
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.

Связанные методы: max_by, minmax, min_by.

none? → true or false Показать исходный код
none?(pattern) → true or false
none? {|element| ... } → true or false
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

Связанные методы: one?, all?, any?.

one? → true or false Показать исходный код
one?(pattern) → true or false
one? {|element| ... } → true or 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

Связанные методы: none?, all?, any?.

partition {|element| ... } → [true_array, false_array] Показать исходный код
partition → enumerator
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.

reduce
Псевдоним для: inject
reject {|element| ... } → array Показать исходный код
reject → enumerator
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.

reverse_each(*args) {|element| ... } → self Показать исходный код
reverse_each(*args) → enumerator
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.

select {|element| ... } → array
select → 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.

Псевдоним для: find_all
slice_after(pattern) → an_enumerator Показать исходный код
slice_after { |elt| bool } → an_enumerator
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"]
slice_before(pattern) → enumerator Показать исходный код
slice_before {|elt| ... } → enumerator
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
  }
}
slice_when {|elt_before, elt_after| bool } → an_enumerator Показать исходный код
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.

sort → array Показать исходный код
sort {|a, b| ... } → array
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. Этот метод реализует преобразование Шварца, полезное, когда вычисление ключа или сравнение обходится дорого.

sort_by {|element| ... } → array Показать исходный код
sort_by → enumerator
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!
sum(initial_value = 0) → number Показать исходный код
sum(initial_value = 0) {|element| ... } → object
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"
take(n) → array Показать исходный код
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]]
take_while {|element| ... } → array Показать исходный код
take_while → enumerator
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.

tally(hash = {}) → hash Показать исходный код
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}
    
to_a(*args) → array Показать исходный код
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]
Также имеет псевдоним: entries
to_h(*args) → hash Показать исходный код
to_h(*args) {|element| ... } → hash
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 не является массивом из двух элементов и блок не передан.

to_set (*args, &block) Показать исходный код
# 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

Создаёт множество из перечисляемого объекта с заданными аргументами. Передача аргументов этому методу устарела.

uniq → array Показать исходный код
uniq {|element| ... } → array
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"]
zip(*other_enums) → array Показать исходный код
zip(*other_enums) {|array| ... } → nil
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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API