Spec-Zone.ru › Ruby 2.3

module Enumerable

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

Публичные методы экземпляра

all? [{ |obj| block } ] → true or false Show source
static VALUE
enum_all(VALUE obj)
{
    struct MEMO *memo = MEMO_NEW(Qtrue, 0, 0);
    rb_block_call(obj, id_each, 0, 0, ENUMFUNC(all), (VALUE)memo);
    return memo->v1;
}

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

%w[ant bear cat].all? { |word| word.length >= 3 } #=> true
%w[ant bear cat].all? { |word| word.length >= 4 } #=> false
[nil, true, 99].all?                              #=> false
any? [{ |obj| block }] → true or false Show source
static VALUE
enum_any(VALUE obj)
{
    struct MEMO *memo = MEMO_NEW(Qfalse, 0, 0);
    rb_block_call(obj, id_each, 0, 0, ENUMFUNC(any), (VALUE)memo);
    return memo->v1;
}

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

%w[ant bear cat].any? { |word| word.length >= 3 } #=> true
%w[ant bear cat].any? { |word| word.length >= 4 } #=> true
[nil, true, 99].any?                              #=> true
chunk { |elt| ... } → an_enumerator Show source
static VALUE
enum_chunk(VALUE enumerable)
{
    VALUE enumerator;

    if (!rb_block_given_p())
        rb_raise(rb_eArgError, "no block given");

    enumerator = rb_obj_alloc(rb_cEnumerator);
    rb_ivar_set(enumerator, rb_intern("chunk_enumerable"), enumerable);
    rb_ivar_set(enumerator, rb_intern("chunk_categorize"), rb_block_proc());
    rb_block_call(enumerator, idInitialize, 0, 0, chunk_i, enumerator);
    return enumerator;
}

Перебирает элементы, группируя их на основе возвращаемого значения блока.

Последовательные элементы, возвращающие одно и то же значение блока, группируются вместе.

Например, последовательные чётные и нечётные числа могут быть сгруппированы следующим образом.

[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5].chunk { |n|
  n.even?
}.each { |even, ary|
  p [even, ary]
}
#=> [false, [3, 1]]
#   [true, [4]]
#   [false, [1, 5, 9]]
#   [true, [2, 6]]
#   [false, [5, 3, 5]]

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

open("/usr/share/dict/words", "r:iso-8859-1") { |f|
  f.chunk { |line| line.ord }.each { |ch, lines| p [ch.chr, lines.length] }
}
#=> ["\n", 1]
#   ["A", 1327]
#   ["B", 1372]
#   ["C", 1507]
#   ["D", 791]
#   ...

Следующие значения ключей имеют особое значение:

  • nil и :_separator указывают, что элементы должны быть пропущены.

  • :_alone указывает, что элемент должен быть сгруппирован сам по себе.

Любые другие символы, начинающиеся с подчеркивания, вызовут ошибку:

items.chunk { |item| :_underscore }
#=> RuntimeError: symbols beginning with an underscore are reserved

nil и :_separator могут использоваться для игнорирования некоторых элементов.

Например, последовательность дефисов в журнале svn может быть устранена следующим образом:

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
}

:_alone может использоваться для принудительного размещения элементов в собственной группе. Например, можно разместить строки, содержащие URL-адрес, сами по себе, а остальные строки сгруппировать вместе, например так:

pattern = /http/
open(filename) { |f|
  f.chunk { |line| line =~ pattern ? :_alone : true }.each { |key, lines|
    pp lines
  }
}
chunk_while {|elt_before, elt_after| bool } → an_enumerator Show source
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, rb_intern("slicewhen_enum"), enumerable);
    rb_ivar_set(enumerator, rb_intern("slicewhen_pred"), pred);
    rb_ivar_set(enumerator, rb_intern("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]]
collect { |obj| block } → array Show source
collect → an_enumerator
static VALUE
enum_collect(VALUE obj)
{
    VALUE ary;

    RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);

    ary = rb_ary_new();
    rb_block_call(obj, id_each, 0, 0, collect_i, ary);

    return ary;
}

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

Если блок не задан, вместо этого возвращается перечислитель.

(1..4).map { |i| i*i }      #=> [1, 4, 9, 16]
(1..4).collect { "cat"  }   #=> ["cat", "cat", "cat", "cat"]
collect_concat { |obj| block } → array Show source
collect_concat → an_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;
}

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

Если блок не задан, вместо этого возвращается перечислитель.

[1, 2, 3, 4].flat_map { |e| [e, -e] } #=> [1, -1, 2, -2, 3, -3, 4, -4]
[[1, 2], [3, 4]].flat_map { |e| e + [100] } #=> [1, 2, 100, 3, 4, 100]
count → int Show source
count(item) → int
count { |obj| block } → int
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 INT2NUM(memo->u3.cnt);
}

Возвращает количество элементов в enum путём перечисления. Если задан аргумент, подсчитывается количество элементов в enum, которые равны item. Если задан блок, он подсчитывает количество элементов, возвращающих истинное значение.

ary = [1, 2, 4, 2]
ary.count               #=> 4
ary.count(2)            #=> 2
ary.count{ |x| x%2==0 } #=> 3
cycle(n=nil) { |obj| block } → nil Show source
cycle(n=nil) → an_enumerator
static VALUE
enum_cycle(int argc, VALUE *argv, VALUE obj)
{
    VALUE ary;
    VALUE nv = Qnil;
    long n, i, len;

    rb_scan_args(argc, argv, "01", &nv);

    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_cycle_size);
    if (NIL_P(nv)) {
        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++) {
            rb_yield(RARRAY_AREF(ary, i));
        }
    }
    return Qnil;
}

Вызывает block для каждого элемента enum многократно n раз или бесконечно, если не указано ни одного или nil задано. Если задано неположительное число или коллекция пуста, ничего не делает. Возвращает nil, если цикл завершился без прерывания.

#cycle сохраняет элементы во внутреннем массиве, поэтому изменения в enum после первого прохода не влияют.

Если блок не задан, вместо этого возвращается перечислитель.

a = ["a", "b", "c"]
a.cycle { |x| puts x }  # print, a, b, c, a, b, c,.. forever.
a.cycle(2) { |x| puts x }  # print, a, b, c, a, b, c.
detect(ifnone = nil) { |obj| block } → obj or nil Show source
detect(ifnone = nil) → an_enumerator
static VALUE
enum_find(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo;
    VALUE if_none;

    rb_scan_args(argc, argv, "01", &if_none);
    RETURN_ENUMERATOR(obj, argc, argv);
    memo = MEMO_NEW(Qundef, 0, 0);
    rb_block_call(obj, id_each, 0, 0, find_i, (VALUE)memo);
    if (memo->u3.cnt) {
        return memo->v1;
    }
    if (!NIL_P(if_none)) {
        return rb_funcallv(if_none, id_call, 0, 0);
    }
    return Qnil;
}

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

Если блок не задан, вместо этого возвращается перечислитель.

(1..10).detect   { |i| i % 5 == 0 and i % 7 == 0 }   #=> nil
(1..100).find    { |i| i % 5 == 0 and i % 7 == 0 }   #=> 35
drop(n) → array Show source
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 элементов из enum и возвращает оставшиеся элементы в массиве.

a = [1, 2, 3, 4, 5, 0]
a.drop(3)             #=> [4, 5, 0]
drop_while { |arr| block } → array Show source
drop_while → an_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;
}

Удаляет элементы вплоть до, но не включая, первый элемент, для которого блок возвращает nil или false, и возвращает массив, содержащий оставшиеся элементы.

Если блок не задан, вместо этого возвращается перечислитель.

a = [1, 2, 3, 4, 5, 0]
a.drop_while { |i| i < 3 }   #=> [3, 4, 5, 0]
each_cons(n) { ... } → nil Show source
each_cons(n) → an_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 Qnil;
    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 Qnil;
}

Итерирует заданный блок для каждого массива из последовательных <n> элементов. Если блок не задан, возвращает перечислитель.

Например:

(1..10).each_cons(3) { |a| p a }
# outputs below
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 10]
each_entry { |obj| block } → enum Show source
each_entry → an_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;
}

Вызывает block один раз для каждого элемента в self, передавая этот элемент в качестве параметра, преобразуя несколько значений из yield в массив.

Если блок не задан, вместо этого возвращается перечислитель.

class Foo
  include Enumerable
  def each
    yield 1
    yield 1, 2
    yield
  end
end
Foo.new.each_entry{ |o| p o }

выдаёт:

1
[1, 2]
nil
each_slice(n) { ... } → nil Show source
each_slice(n) → an_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 Qnil;
}

Итерирует заданный блок для каждого среза из <n> элементов. Если блок не задан, возвращает перечислитель.

(1..10).each_slice(3) { |a| p a }
# outputs below
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]
each_with_index(*args) { |obj, i| block } → enum Show source
each_with_index(*args) → an_enumerator
static VALUE
enum_each_with_index(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo;

    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);

    memo = MEMO_NEW(0, 0, 0);
    rb_block_call(obj, id_each, argc, argv, each_with_index_i, (VALUE)memo);
    return obj;
}

Вызывает block с двумя аргументами: элемент и его индекс, для каждого элемента в enum. Заданные аргументы передаются в each().

Если блок не задан, вместо этого возвращается перечислитель.

hash = Hash.new
%w(cat dog wombat).each_with_index { |item, index|
  hash[item] = index
}
hash   #=> {"cat"=>0, "dog"=>1, "wombat"=>2}
each_with_object(obj) { |(*args), memo_obj| ... } → obj Show source
each_with_object(obj) → an_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;
}

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

Если блок не задан, возвращает перечислитель.

evens = (1..10).each_with_object([]) { |i, a| a << i*2 }
#=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
entries(*args) → array Show source
static VALUE
enum_to_a(int argc, VALUE *argv, VALUE obj)
{
    VALUE ary = rb_ary_new();

    rb_block_call(obj, id_each, argc, argv, collect_all, ary);
    OBJ_INFECT(ary, obj);

    return ary;
}

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

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

require 'prime'
Prime.entries 10                  #=> [2, 3, 5, 7]
find(ifnone = nil) { |obj| block } → obj or nil Show source
find(ifnone = nil) → an_enumerator
static VALUE
enum_find(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo;
    VALUE if_none;

    rb_scan_args(argc, argv, "01", &if_none);
    RETURN_ENUMERATOR(obj, argc, argv);
    memo = MEMO_NEW(Qundef, 0, 0);
    rb_block_call(obj, id_each, 0, 0, find_i, (VALUE)memo);
    if (memo->u3.cnt) {
        return memo->v1;
    }
    if (!NIL_P(if_none)) {
        return rb_funcallv(if_none, id_call, 0, 0);
    }
    return Qnil;
}

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

Если блок не задан, вместо этого возвращается перечислитель.

(1..10).detect   { |i| i % 5 == 0 and i % 7 == 0 }   #=> nil
(1..100).find    { |i| i % 5 == 0 and i % 7 == 0 }   #=> 35
find_all { |obj| block } → array Show source
find_all → an_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;
}

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

Если блок не задан, вместо этого возвращается Enumerator.

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

[1,2,3,4,5].select { |num|  num.even?  }   #=> [2, 4]

См. также #reject.

find_index(value) → int or nil Show source
find_index { |obj| block } → int or nil
find_index → an_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;
}

Сравнивает каждый элемент в enum с value или передает в block. Возвращает индекс первого, для которого вычисленное значение истинно. Если ни один объект не соответствует, возвращает nil

Если ни блок, ни аргумент не заданы, вместо этого возвращается перечислитель.

(1..10).find_index  { |i| i % 5 == 0 and i % 7 == 0 }  #=> nil
(1..100).find_index { |i| i % 5 == 0 and i % 7 == 0 }  #=> 34
(1..100).find_index(50)                                #=> 49
first → obj or nil Show source
first(n) → an_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;
    }
}

Возвращает первый элемент или первые n элементов перечисляемого объекта. Если перечисляемый объект пуст, первая форма возвращает nil, а вторая форма возвращает пустой массив.

%w[foo bar baz].first     #=> "foo"
%w[foo bar baz].first(2)  #=> ["foo", "bar"]
%w[foo bar baz].first(10) #=> ["foo", "bar", "baz"]
[].first                  #=> nil
[].first(10)              #=> []
flat_map { |obj| block } → array Show source
flat_map → an_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;
}

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

Если блок не задан, вместо этого возвращается перечислитель.

[1, 2, 3, 4].flat_map { |e| [e, -e] } #=> [1, -1, 2, -2, 3, -3, 4, -4]
[[1, 2], [3, 4]].flat_map { |e| e + [100] } #=> [1, 2, 100, 3, 4, 100]
grep(pattern) → array Show source
grep(pattern) { |obj| block } → array
static VALUE
enum_grep(VALUE obj, VALUE pat)
{
    VALUE ary = rb_ary_new();
    struct MEMO *memo = MEMO_NEW(pat, ary, Qtrue);

    rb_block_call(obj, id_each, 0, 0, rb_block_given_p() ? grep_iter_i : grep_i, (VALUE)memo);

    return ary;
}

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

(1..100).grep 38..44   #=> [38, 39, 40, 41, 42, 43, 44]
c = IO.constants
c.grep(/SEEK/)         #=> [:SEEK_SET, :SEEK_CUR, :SEEK_END]
res = c.grep(/SEEK/) { |v| IO.const_get(v) }
res                    #=> [0, 1, 2]
grep_v(pattern) → array Show source
grep_v(pattern) { |obj| block } → array
static VALUE
enum_grep_v(VALUE obj, VALUE pat)
{
    VALUE ary = rb_ary_new();
    struct MEMO *memo = MEMO_NEW(pat, ary, Qfalse);

    rb_block_call(obj, id_each, 0, 0, rb_block_given_p() ? grep_iter_i : grep_i, (VALUE)memo);

    return ary;
}

Инвертированная версия #grep. Возвращает массив каждого элемента в enum, для которого не Pattern === element.

(1..10).grep_v 2..5   #=> [1, 6, 7, 8, 9, 10]
res =(1..10).grep_v(2..5) { |v| v * 2 }
res                    #=> [2, 12, 14, 16, 18, 20]
group_by { |obj| block } → a_hash Show source
group_by → an_enumerator
static VALUE
enum_group_by(VALUE obj)
{
    VALUE hash;

    RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);

    hash = rb_hash_new();
    rb_block_call(obj, id_each, 0, 0, group_by_i, hash);
    OBJ_INFECT(hash, obj);

    return hash;
}

Группирует коллекцию по результату блока. Возвращает хэш, где ключи — это результат вычисления блока, а значения — массивы элементов в коллекции, которые соответствуют ключу.

Если блок не задан, возвращается перечислитель.

(1..6).group_by { |i| i%3 }   #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}
include?(obj) → true or false Show source
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;
}

Возвращает true, если какой-либо член enum равен obj. Равенство проверяется с использованием ==.

IO.constants.include? :SEEK_SET          #=> true
IO.constants.include? :SEEK_NO_FURTHER   #=> false
IO.constants.member? :SEEK_SET          #=> true
IO.constants.member? :SEEK_NO_FURTHER   #=> false
inject(initial, sym) → obj Show source
inject(sym) → obj
inject(initial) { |memo, obj| block } → obj
inject { |memo, obj| block } → obj
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;

    switch (rb_scan_args(argc, argv, "02", &init, &op)) {
      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;
    }
    memo = MEMO_NEW(init, Qnil, op);
    rb_block_call(obj, id_each, 0, 0, iter, (VALUE)memo);
    if (memo->v1 == Qundef) return Qnil;
    return memo->v1;
}

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

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

Если вы не указываете явно initial значение для memo, то первый элемент коллекции используется в качестве начального значения для memo.

# Sum some numbers
(5..10).reduce(:+)                             #=> 45
# Same using a block and inject
(5..10).inject { |sum, n| sum + n }            #=> 45
# Multiply some numbers
(5..10).reduce(1, :*)                          #=> 151200
# Same using a block
(5..10).inject(1) { |product, n| product * n } #=> 151200
# find the longest word
longest = %w{ cat sheep bear }.inject do |memo, word|
   memo.length > word.length ? memo : word
end
longest                                        #=> "sheep"
lazy → lazy_enumerator Show source
static VALUE
enumerable_lazy(VALUE obj)
{
    VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size);
    /* Qfalse indicates that the Enumerator::Lazy has no method name */
    rb_ivar_set(result, id_method, Qfalse);
    return result;
}

Возвращает ленивый перечислитель, чьи методы map/collect, flat_map/collect_concat, select/find_all, reject, grep, #grep_v, zip, take, #take_while, drop и #drop_while перечисляют значения только по мере необходимости. Однако, если блоку zip передан блок, значения перечисляются немедленно.

Пример

Следующая программа находит пифагоровы тройки:

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 { |obj| block } → array Show source
map → an_enumerator
static VALUE
enum_collect(VALUE obj)
{
    VALUE ary;

    RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);

    ary = rb_ary_new();
    rb_block_call(obj, id_each, 0, 0, collect_i, ary);

    return ary;
}

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

Если блок не задан, вместо этого возвращается перечислитель.

(1..4).map { |i| i*i }      #=> [1, 4, 9, 16]
(1..4).collect { "cat"  }   #=> ["cat", "cat", "cat", "cat"]
max → obj Show source
max { |a, b| block } → obj
max(n) → obj
max(n) {|a,b| block } → obj
static VALUE
enum_max(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo = MEMO_NEW(Qundef, 0, 0);
    VALUE result;
    VALUE num;

    rb_scan_args(argc, argv, "01", &num);

    if (!NIL_P(num))
       return nmin_run(obj, num, 0, 1);

    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 = memo->v1;
    if (result == Qundef) return Qnil;
    return result;
}

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

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

Если указан аргумент n, возвращаются максимальные n элементов в виде отсортированного по убыванию массива.

a = %w[albatross dog horse]
a.max(2)                                  #=> ["horse", "dog"]
a.max(2) {|a, b| a.length <=> b.length }  #=> ["albatross", "horse"]
[5, 1, 3, 4, 2].max(3)                    #=> [5, 4, 3]
max_by {|obj| block } → obj Show source
max_by → an_enumerator
max_by(n) {|obj| block } → obj
max_by(n) → an_enumerator
static VALUE
enum_max_by(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo;
    VALUE num;

    rb_scan_args(argc, argv, "01", &num);

    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);

    if (!NIL_P(num))
        return nmin_run(obj, num, 1, 1);

    memo = MEMO_NEW(Qundef, Qnil, 0);
    rb_block_call(obj, id_each, 0, 0, max_by_i, (VALUE)memo);
    return memo->v2;
}

Возвращает объект в enum, который дает максимальное значение из заданного блока.

Если блок не задан, возвращается перечислитель.

a = %w(albatross dog horse)
a.max_by { |x| x.length }   #=> "albatross"

Если указан аргумент n, возвращаются максимальные n элементов в виде массива. Эти n элементы отсортированы по значению из заданного блока в порядке убывания.

a = %w[albatross dog horse]
a.max_by(2) {|x| x.length } #=> ["albatross", "horse"]

enum.max_by(n) может использоваться для реализации взвешенной случайной выборки. Следующий пример реализует и использует Enumerable#wsample.

module Enumerable
  # weighted random sampling.
  #
  # Pavlos S. Efraimidis, Paul G. Spirakis
  # Weighted random sampling with a reservoir
  # Information Processing Letters
  # Volume 97, Issue 5 (16 March 2006)
  def wsample(n)
    self.max_by(n) {|v| rand ** (1.0/yield(v)) }
  end
end
e = (-20..20).to_a*10000
a = e.wsample(20000) {|x|
  Math.exp(-(x/5.0)**2) # normal distribution
}
# a is 20000 samples from e.
p a.length #=> 20000
h = a.group_by {|x| x }
-10.upto(10) {|x| puts "*" * (h[x].length/30.0).to_i if h[x] }
#=> *
#   ***
#   ******
#   ***********
#   ******************
#   *****************************
#   *****************************************
#   ****************************************************
#   ***************************************************************
#   ********************************************************************
#   ***********************************************************************
#   ***********************************************************************
#   **************************************************************
#   ****************************************************
#   ***************************************
#   ***************************
#   ******************
#   ***********
#   *******
#   ***
#   *
member?(obj) → true or false Show source
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;
}

Возвращает true, если какой-либо член enum равен obj. Равенство проверяется с помощью ==.

IO.constants.include? :SEEK_SET          #=> true
IO.constants.include? :SEEK_NO_FURTHER   #=> false
IO.constants.member? :SEEK_SET          #=> true
IO.constants.member? :SEEK_NO_FURTHER   #=> false
min → obj Show source
min {| a,b | block } → obj
min(n) → array
min(n) {| a,b | block } → array
static VALUE
enum_min(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo = MEMO_NEW(Qundef, 0, 0);
    VALUE result;
    VALUE num;

    rb_scan_args(argc, argv, "01", &num);

    if (!NIL_P(num))
       return nmin_run(obj, num, 0, 0);

    if (rb_block_given_p()) {
        rb_block_call(obj, id_each, 0, 0, min_ii, (VALUE)memo);
    }
    else {
        rb_block_call(obj, id_each, 0, 0, min_i, (VALUE)memo);
    }
    result = memo->v1;
    if (result == Qundef) return Qnil;
    return result;
}

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

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

Если указан аргумент n, возвращаются минимальные n элементов в виде отсортированного массива.

a = %w[albatross dog horse]
a.min(2)                                  #=> ["albatross", "dog"]
a.min(2) {|a, b| a.length <=> b.length }  #=> ["dog", "horse"]
[5, 1, 3, 4, 2].min(3)                    #=> [1, 2, 3]
min_by {|obj| block } → obj Show source
min_by → an_enumerator
min_by(n) {|obj| block } → array
min_by(n) → an_enumerator
static VALUE
enum_min_by(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo;
    VALUE num;

    rb_scan_args(argc, argv, "01", &num);

    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);

    if (!NIL_P(num))
        return nmin_run(obj, num, 1, 0);

    memo = MEMO_NEW(Qundef, Qnil, 0);
    rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo);
    return memo->v2;
}

Возвращает объект в enum, который дает минимальное значение из заданного блока.

Если блок не задан, возвращается перечислитель.

a = %w(albatross dog horse)
a.min_by { |x| x.length }   #=> "dog"

Если указан аргумент n, возвращаются минимальные n элементов в виде массива. Эти n элементы отсортированы по значению из заданного блока.

a = %w[albatross dog horse]
p a.min_by(2) {|x| x.length } #=> ["dog", "horse"]
minmax → [min, max] Show source
minmax { |a, b| block } → [min, max]
static VALUE
enum_minmax(VALUE obj)
{
    struct MEMO *memo = MEMO_NEW(Qundef, Qundef, Qundef);
    struct minmax_t *m = (struct minmax_t *)&memo->v1;

    m->min = Qundef;
    m->last = Qundef;
    if (rb_block_given_p()) {
        rb_block_call(obj, id_each, 0, 0, minmax_ii, (VALUE)memo);
        if (m->last != Qundef)
            minmax_ii_update(m->last, m->last, m);
    }
    else {
        rb_block_call(obj, id_each, 0, 0, minmax_i, (VALUE)memo);
        if (m->last != Qundef)
            minmax_i_update(m->last, m->last, m);
    }
    if (m->min != Qundef) {
        return rb_assoc_new(m->min, m->max);
    }
    return rb_assoc_new(Qnil, Qnil);
}

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

a = %w(albatross dog horse)
a.minmax                                  #=> ["albatross", "horse"]
a.minmax { |a, b| a.length <=> b.length } #=> ["dog", "albatross"]
minmax_by { |obj| block } → [min, max] Show source
minmax_by → an_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 (m->last_bv != Qundef)
        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);
}

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

Если блок не задан, возвращается перечислитель.

a = %w(albatross dog horse)
a.minmax_by { |x| x.length }   #=> ["dog", "albatross"]
none? [{ |obj| block }] → true or false Show source
static VALUE
enum_none(VALUE obj)
{
    struct MEMO *memo = MEMO_NEW(Qtrue, 0, 0);
    rb_block_call(obj, id_each, 0, 0, ENUMFUNC(none), (VALUE)memo);
    return memo->v1;
}

Передает каждый элемент коллекции в заданный блок. Метод возвращает true, если блок никогда не возвращает true для всех элементов. Если блок не задан, none? вернет true, только если ни один из членов коллекции не является истинным.

%w{ant bear cat}.none? { |word| word.length == 5 } #=> true
%w{ant bear cat}.none? { |word| word.length >= 4 } #=> false
[].none?                                           #=> true
[nil].none?                                        #=> true
[nil, false].none?                                 #=> true
[nil, false, true].none?                           #=> false
one? [{ |obj| block }] → true or false Show source
static VALUE
enum_one(VALUE obj)
{
    struct MEMO *memo = MEMO_NEW(Qundef, 0, 0);
    VALUE result;

    rb_block_call(obj, id_each, 0, 0, ENUMFUNC(one), (VALUE)memo);
    result = memo->v1;
    if (result == Qundef) return Qfalse;
    return result;
}

Передает каждый элемент коллекции в заданный блок. Метод возвращает true, если блок возвращает true ровно один раз. Если блок не задан, one? вернет true, только если ровно один из членов коллекции является истинным.

%w{ant bear cat}.one? { |word| word.length == 4 }  #=> true
%w{ant bear cat}.one? { |word| word.length > 4 }   #=> false
%w{ant bear cat}.one? { |word| word.length < 4 }   #=> false
[ nil, true, 99 ].one?                             #=> false
[ nil, true, false ].one?                          #=> true
partition { |obj| block } → [ true_array, false_array ] Show source
partition → an_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);
}

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

Если блок не задан, возвращается перечислитель.

(1..6).partition { |v| v.even? }  #=> [[2, 4, 6], [1, 3, 5]]
reduce(initial, sym) → obj Show source
reduce(sym) → obj
reduce(initial) { |memo, obj| block } → obj
reduce { |memo, obj| block } → obj
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;

    switch (rb_scan_args(argc, argv, "02", &init, &op)) {
      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;
    }
    memo = MEMO_NEW(init, Qnil, op);
    rb_block_call(obj, id_each, 0, 0, iter, (VALUE)memo);
    if (memo->v1 == Qundef) return Qnil;
    return memo->v1;
}

Объединяет все элементы enum, применяя бинарную операцию, заданную блоком или символом, обозначающим метод или оператор.

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

Если вы не указываете явно начальное значение для initial, то первый элемент коллекции используется в качестве начального значения memo.

# Sum some numbers
(5..10).reduce(:+)                             #=> 45
# Same using a block and inject
(5..10).inject { |sum, n| sum + n }            #=> 45
# Multiply some numbers
(5..10).reduce(1, :*)                          #=> 151200
# Same using a block
(5..10).inject(1) { |product, n| product * n } #=> 151200
# find the longest word
longest = %w{ cat sheep bear }.inject do |memo, word|
   memo.length > word.length ? memo : word
end
longest                                        #=> "sheep"
reject { |obj| block } → array Show source
reject → an_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;
}

Возвращает массив для всех элементов enum, для которых заданный block возвращает false.

Если блок не задан, возвращается Enumerator.

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

[1, 2, 3, 4, 5].reject { |num| num.even? } #=> [1, 3, 5]

См. также #find_all.

reverse_each(*args) { |item| block } → enum Show source
reverse_each(*args) → an_enumerator
static VALUE
enum_reverse_each(int argc, VALUE *argv, VALUE obj)
{
    VALUE ary;
    long i;

    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);

    ary = enum_to_a(argc, argv, obj);

    for (i = RARRAY_LEN(ary); --i >= 0; ) {
        rb_yield(RARRAY_AREF(ary, i));
    }

    return obj;
}

Создает временный массив и обходит этот массив в обратном порядке.

Если блок не задан, возвращается перечислитель.

  (1..3).reverse_each { |v| p v }

produces:

  3
  2
  1
select { |obj| block } → array Show source
select → an_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;
}

Возвращает массив, содержащий все элементы enum, для которых данный block возвращает значение true.

Если блок не задан, вместо этого возвращается Enumerator.

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

[1,2,3,4,5].select { |num|  num.even?  }   #=> [2, 4]

См. также #reject.

slice_after(pattern) → an_enumerator Show source
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, rb_intern("sliceafter_enum"), enumerable);
    rb_ivar_set(enumerator, rb_intern("sliceafter_pat"), pat);
    rb_ivar_set(enumerator, rb_intern("sliceafter_pred"), pred);

    rb_block_call(enumerator, idInitialize, 0, 0, sliceafter_i, enumerator);
    return enumerator;
}

Создает перечислитель для каждого фрагментированного элемента. Концы фрагментов определяются pattern и блоком.

Если 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) → an_enumerator Show source
slice_before { |elt| bool } → an_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, rb_intern("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, rb_intern("slicebefore_sep_pat"), sep_pat);
    }
    rb_ivar_set(enumerator, rb_intern("slicebefore_enumerable"), enumerable);
    rb_block_call(enumerator, idInitialize, 0, 0, slicebefore_i, enumerator);
    return enumerator;
}

Создает перечислитель для каждого фрагментированного элемента. Начала фрагментов определяются pattern и блоком.

Если pattern === elt возвращает true или блок возвращает true для элемента, элемент является началом фрагмента.

=== и блок вызываются от первого элемента до последнего элемента enum. Результат для первого элемента игнорируется.

Результирующий перечислитель возвращает фрагментированные элементы в виде массива. Таким образом, метод each может быть вызван следующим образом:

enum.slice_before(pattern).each { |ary| ... }
enum.slice_before { |elt| bool }.each { |ary| ... }

Другие методы класса Enumerator и модуля Enumerable, такие как 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"]
#   ...

Если блоку необходимо сохранять состояние для нескольких элементов, можно использовать локальные переменные. Например, три или более последовательных возрастающих чисел могут быть объединены следующим образом:

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 Show source
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, rb_intern("slicewhen_enum"), enumerable);
    rb_ivar_set(enumerator, rb_intern("slicewhen_pred"), pred);
    rb_ivar_set(enumerator, rb_intern("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]]

Абзацы (непустые строки с пустыми строками в конце) могут быть разбиты на фрагменты следующим образом: (См. #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"]]
sort → array Show source
sort { |a, b| block } → array
static VALUE
enum_sort(VALUE obj)
{
    return rb_ary_sort(enum_to_a(0, 0, obj));
}

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

%w(rhea kea flea).sort          #=> ["flea", "kea", "rhea"]
(1..10).sort { |a, b| b <=> a }  #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
sort_by { |obj| block } → array Show source
sort_by → an_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_tmp_new(SORT_BY_BUFSIZE*2);
    rb_ary_store(buf, SORT_BY_BUFSIZE*2-1, Qnil);
    memo = MEMO_NEW(0, 0, 0);
    OBJ_INFECT(memo, obj);
    data = (struct sort_by_data *)&memo->v1;
    RB_OBJ_WRITE(memo, &data->ary, ary);
    RB_OBJ_WRITE(memo, &data->buf, buf);
    data->n = 0;
    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) {
        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);
    OBJ_INFECT(ary, memo);

    return ary;
}

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

Если блок не задан, вместо этого возвращается перечислитель.

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

Текущая реализация 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"]
take(n) → array Show source
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 элементов из enum.

a = [1, 2, 3, 4, 5, 0]
a.take(3)             #=> [1, 2, 3]
a.take(30)            #=> [1, 2, 3, 4, 5, 0]
take_while { |arr| block } → array Show source
take_while → an_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;
}

Передает элементы в блок, пока блок не вернет nil или false, затем прекращает итерацию и возвращает массив всех предыдущих элементов.

Если блок не задан, вместо этого возвращается перечислитель.

a = [1, 2, 3, 4, 5, 0]
a.take_while { |i| i < 3 }   #=> [1, 2]
to_a(*args) → array Show source
static VALUE
enum_to_a(int argc, VALUE *argv, VALUE obj)
{
    VALUE ary = rb_ary_new();

    rb_block_call(obj, id_each, argc, argv, collect_all, ary);
    OBJ_INFECT(ary, obj);

    return ary;
}

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

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

require 'prime'
Prime.entries 10                  #=> [2, 3, 5, 7]
to_h(*args) → hash Show source
static VALUE
enum_to_h(int argc, VALUE *argv, VALUE obj)
{
    VALUE hash = rb_hash_new();
    rb_block_call(obj, id_each, argc, argv, enum_to_h_i, hash);
    OBJ_INFECT(hash, obj);
    return hash;
}

Возвращает результат интерпретации enum как списка пар [key, value].

%[hello world].each_with_index.to_h
  # => {:hello => 0, :world => 1}
to_set(klass = Set, *args, &block) Show source
# File lib/set.rb, line 702
def to_set(klass = Set, *args, &block)
  klass.new(self, *args, &block)
end

Создает множество из перечислимого объекта с заданными аргументами. Для использования этого метода необходимо +require “set”+.

zip(arg, ...) → an_array_of_array Показать исходный код
zip(arg, ...) { |arr| block } → 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) {
        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_funcall(argv[i], conv, 1, ID2SYM(id_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;
}

Берёт один элемент из enum и объединяет соответствующие элементы из каждого args. Это создаёт последовательность n-элементных массивов, где n на единицу больше, чем количество аргументов. Длина результирующей последовательности будет enum#size. Если размер любого аргумента меньше, чем enum#size, используются значения nil. Если предоставлен блок, он вызывается для каждого выходного массива; в противном случае возвращается массив массивов.

a = [ 4, 5, 6 ]
b = [ 7, 8, 9 ]

a.zip(b)                 #=> [[4, 7], [5, 8], [6, 9]]
[1, 2, 3].zip(a, b)      #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
[1, 2].zip(a, b)         #=> [[1, 4, 7], [2, 5, 8]]
a.zip([1, 2], [8])       #=> [[4, 1, 8], [5, 2, nil], [6, nil, nil]]

c = []
a.zip(b) { |x, y| c << x + y }  #=> nil
c                               #=> [11, 13, 15]

Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

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