модуль Enumerable
Mixin Enumerable предоставляет классам коллекций несколько методов обхода и поиска, а также возможность сортировки. Класс должен предоставить метод each, который возвращает последовательные члены коллекции. Если используется Enumerable#max, min или sort, объекты в коллекции также должны реализовывать осмысленный оператор <=>, так как эти методы полагаются на порядок между членами коллекции.
Методы публичного экземпляра
static VALUE
enum_all(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
WARN_UNUSED_BLOCK(argc);
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.
Если вместо этого задан шаблон, метод возвращает, соответствует ли pattern === element каждый член коллекции.
%w[ant bear cat].all? { |word| word.length >= 3 } #=> true
%w[ant bear cat].all? { |word| word.length >= 4 } #=> false
%w[ant bear cat].all?(/t/) #=> false
[1, 2i, 3.14].all?(Numeric) #=> true
[nil, true, 99].all? #=> false
[].all? #=> true
static VALUE
enum_any(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qfalse);
WARN_UNUSED_BLOCK(argc);
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.
Если вместо этого задан шаблон, метод возвращает, соответствует ли pattern === element какой-либо член коллекции.
%w[ant bear cat].any? { |word| word.length >= 3 } #=> true
%w[ant bear cat].any? { |word| word.length >= 4 } #=> true
%w[ant bear cat].any?(/d/) #=> false
[nil, true, 99].any?(Integer) #=> true
[nil, true, 99].any? #=> true
[].any? #=> false
static VALUE
enum_chain(int argc, VALUE *argv, VALUE obj)
{
VALUE enums = rb_ary_new_from_values(1, &obj);
rb_ary_cat(enums, argv, argc);
return enum_chain_initialize(enum_chain_allocate(rb_cEnumChain), enums);
} Возвращает объект перечисления, сгенерированный из этого перечисления и заданных перечислений.
e = (1..3).chain([4, 5]) e.to_a #=> [1, 2, 3, 4, 5]
static VALUE
enum_chunk(VALUE enumerable)
{
VALUE enumerator;
RETURN_SIZED_ENUMERATOR(enumerable, 0, 0, enum_size);
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, 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.upcase.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 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
}
:_alone можно использовать для принудительного включения элементов в свою собственную группу. Например, вы можете поместить строки, содержащие URL, сами по себе, а остальные строки сгруппировать вместе, как в этом примере:
pattern = /http/
open(filename) { |f|
f.chunk { |line| line =~ pattern ? :_alone : true }.each { |key, lines|
pp lines
}
}
Если блок не задан, вместо этого возвращается перечисление для `chunk`.
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]
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;
} Вызывает блок один раз для каждого элемента в 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
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]
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;
} Вызывает блок с двумя аргументами, элементом и его индексом, для каждого элемента в перечислителе. Переданные аргументы передаются в each().
Если блок не задан, вместо этого возвращается итератор.
hash = Hash.new
%w(cat dog wombat).each_with_index { |item, index|
hash[item] = index
}
hash #=> {"cat"=>0, "dog"=>1, "wombat"=>2}
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]
Возвращает массив, содержащий элементы в перечислителе.
(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]
Возвращает массив, содержащий все элементы enum, для которых заданный block возвращает значение true.
Методы find_all и select являются псевдонимами. Ни один из них не дает преимущества по производительности.
Если блок не задан, вместо этого возвращается Enumerator.
(1..10).find_all { |i| i % 3 == 0 } #=> [3, 6, 9]
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
[:foo, :bar].filter { |x| x == :foo } #=> [:foo]
См. также Enumerable#reject, Enumerable#grep.
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;
} Возвращает новый массив, содержащий истинные результаты (все, кроме false или nil выполнения block для каждого элемента в enum.
Если блок не задан, вместо этого возвращается Enumerator.
(1..10).filter_map { |i| i * 2 if i.even? } #=> [4, 8, 12, 16, 20]
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);
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;
} Передает каждый элемент в перечислителе в блок. Возвращает первый, для которого блок не равен false. Если объект не найден, вызывает если_нет и возвращает его результат, если он указан, или возвращает nil в противном случае.
Если блок не задан, вместо этого возвращается итератор.
(1..100).detect #=> #<Enumerator: 1..100:detect>
(1..100).find #=> #<Enumerator: 1..100:find>
(1..10).detect { |i| i % 5 == 0 && i % 7 == 0 } #=> nil
(1..10).find { |i| i % 5 == 0 && i % 7 == 0 } #=> nil
(1..10).detect(-> {0}) { |i| i % 5 == 0 && i % 7 == 0 } #=> 0
(1..10).find(-> {0}) { |i| i % 5 == 0 && i % 7 == 0 } #=> 0
(1..100).detect { |i| i % 5 == 0 && i % 7 == 0 } #=> 35
(1..100).find { |i| i % 5 == 0 && i % 7 == 0 } #=> 35
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.
Методы find_all и select являются псевдонимами. Ни один из них не дает преимущества по производительности.
Если блок не задан, вместо этого возвращается Enumerator.
(1..10).find_all { |i| i % 3 == 0 } #=> [3, 6, 9]
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
[:foo, :bar].filter { |x| x == :foo } #=> [:foo]
См. также Enumerable#reject, Enumerable#grep.
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;
} Сравнивает каждый элемент в перечислителе с значением или передает в блок. Возвращает индекс первого, для которого вычисленное значение не равно false. Если объект не найден, возвращает nil.
Если ни блок, ни аргумент не заданы, возвращается итератор вместо этого.
(1..10).find_index { |i| i % 5 == 0 && i % 7 == 0 } #=> nil
(1..100).find_index { |i| i % 5 == 0 && i % 7 == 0 } #=> 34
(1..100).find_index(50) #=> 49
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) #=> []
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;
} Возвращает новый массив, содержащий конкатенированные результаты выполнения блока один раз для каждого элемента в перечислителе.
Если блок не задан, возвращается итератор вместо этого.
[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]
static VALUE
enum_grep(VALUE obj, VALUE pat)
{
return enum_grep0(obj, pat, Qtrue);
} Возвращает массив каждого элемента в перечислителе, для которого Pattern === element. Если необязательный блок предоставлен, каждый соответствующий элемент передается в него, и результат блока сохраняется в выходном массиве.
(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]
static VALUE
enum_grep_v(VALUE obj, VALUE pat)
{
return enum_grep0(obj, pat, Qfalse);
} Обратная версия Enumerable#grep. Возвращает массив каждого элемента в перечислителе, для которого не 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]
static VALUE
enum_group_by(VALUE obj)
{
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
return enum_hashify(obj, 0, 0, group_by_i);
} Группирует коллекцию по результату блока. Возвращает словарь, где ключами являются значения, полученные в результате выполнения блока, а значениями — массивы элементов из коллекции, соответствующие ключу.
Если блок не задан, возвращается перечислитель.
(1..6).group_by { |i| i%3 } #=> {0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}
Возвращает true если любой член enum равен obj. Равенство проверяется с помощью ==.
(1..10).include? 5 #=> true (1..10).include? 15 #=> false (1..10).member? 5 #=> true (1..10).member? 15 #=> false
static VALUE
enum_inject(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo;
VALUE init, op;
rb_block_call_func *iter = inject_i;
ID id;
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;
}
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 (memo->v1 == Qundef) return Qnil;
return memo->v1;
} Объединяет все элементы enum, применяя бинарную операцию, заданную блоком или символом, обозначающим метод или оператор.
Методы inject и reduce являются псевдонимами. Разницы в производительности нет.
Если вы указываете блок, то для каждого элемента в 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"
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
Возвращает новый массив с результатами выполнения block один раз для каждого элемента в enum.
Если блок не задан, вместо него возвращается перечислитель.
(1..4).map { |i| i*i } #=> [1, 4, 9, 16]
(1..4).collect { "cat" } #=> ["cat", "cat", "cat", "cat"]
static VALUE
enum_max(int argc, VALUE *argv, VALUE obj)
{
VALUE memo;
struct max_t *m = NEW_CMP_OPT_MEMO(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;
m->cmp_opt.opt_methods = 0;
m->cmp_opt.opt_inited = 0;
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 (result == Qundef) return Qnil;
return result;
} Возвращает объект в enum с максимальным значением. Первый вариант предполагает, что все объекты реализуют <=>; второй использует блок для возврата 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]
static VALUE
enum_one(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qundef);
VALUE result;
WARN_UNUSED_BLOCK(argc);
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 только если ровно один из элементов коллекции является истинным.
Если вместо этого указан шаблон, метод возвращает, соответствует ли pattern === element ровно одному элементу коллекции.
%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
%w{ant bear cat}.one?(/t/) #=> false
[ nil, true, 99 ].one? #=> false
[ nil, true, false ].one? #=> true
[ nil, true, 99 ].one?(Integer) #=> true
[].one? #=> false
static VALUE
enum_partition(VALUE obj)
{
struct MEMO *memo;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
memo = MEMO_NEW(rb_ary_new(), rb_ary_new(), 0);
rb_block_call(obj, id_each, 0, 0, partition_i, (VALUE)memo);
return rb_assoc_new(memo->v1, memo->v2);
} Возвращает два массива: первый содержит элементы enum, для которых блок возвращает значение true, второй — остальные элементы.
Если блок не указан, возвращается перечислитель.
(1..6).partition { |v| v.even? } #=> [[2, 4, 6], [1, 3, 5]]
Комбинирует все элементы enum, применяя бинарную операцию, заданную блоком или символом, обозначающим метод или оператор.
Методы inject и reduce являются псевдонимами. Никакой производительности от одного из них не выигрываете.
Если вы указываете блок, то для каждого элемента в enum блоку передаётся значение аккумулятора (memo) и элемент. Если вы указываете символ вместо этого, то каждый элемент в коллекции будет передан в метод с заданным именем объекта memo. В любом случае результат становится новым значением для memo. В конце итерации конечное значение memo является возвращаемым значением для метода.
Если вы не явно не задаёте начальное значение для 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"
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]
См. также Enumerable#find_all.
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;
} Создаёт временный массив и обходит его в обратном порядке.
Если блок не указан, возвращается перечислитель.
(1..3).reverse_each { |v| p v }
выводит:
3 2 1
Возвращает массив, содержащий все элементы enum, для которых заданный block возвращает значение true.
Методы find_all и select являются псевдонимами. Никакой производительности от одного из них не выигрываете.
Если блок не указан, вместо этого возвращается Enumerator.
(1..10).find_all { |i| i % 3 == 0 } #=> [3, 6, 9]
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
[:foo, :bar].filter { |x| x == :foo } #=> [:foo]
См. также Enumerable#reject, Enumerable#grep.
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"]
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, такие как 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. Таким образом, каждое письмо может быть извлечено с помощью slice before Unix From line.
# parse mbox
open("mbox") { |f|
f.slice_before { |line|
line.start_with? "From "
}.each { |mail|
unix_from = mail.shift
i = mail.index("\n")
header = mail[0...i]
body = mail[(i+1)..-1]
body.pop if body.last == "\n"
fields = header.slice_before { |line| !" \t".include?(line[0]) }.to_a
p unix_from
pp fields
pp body
}
}
# split mails in mbox (slice before Unix From line after an empty line)
open("mbox") { |f|
emp = true
f.slice_before { |line|
prevemp = emp
emp = line == "\n"
prevemp && line.start_with?("From ")
}.each { |mail|
mail.pop if mail.last == "\n"
pp mail
}
}
static VALUE
enum_slice_when(VALUE enumerable)
{
VALUE enumerator;
VALUE pred;
pred = rb_block_proc();
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, 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]]
Абзацы (непустые строки с последующими пустыми строками) могут быть разбиты на фрагменты следующим образом: (см. Enumerable#chunk, чтобы пропустить пустые строки.)
lines = ["foo\n", "bar\n", "\n", "baz\n", "qux\n"]
p lines.slice_when {|l1, l2| /\A\s*\z/ =~ l1 && /\S/ =~ l2 }.to_a
#=> [["foo\n", "bar\n", "\n"], ["baz\n", "qux\n"]]
Enumerable#chunk_while делает то же самое, за исключением разделения, когда блок возвращает false вместо true.
static VALUE
enum_sort(VALUE obj)
{
return rb_ary_sort_bang(enum_to_a(0, 0, obj));
} Возвращает массив, содержащий элементы перечисления в отсортированном порядке.
Сравнения для сортировки будут выполняться с использованием собственного оператора элементов перечисления или с использованием необязательного кодового блока.
Блок должен реализовывать сравнение между a и b и возвращать целое число меньше 0, когда b следует за a, целое число равно 0, когда a и b эквивалентны, или целое число больше 0, когда a следует за b.
Результат не гарантируется как стабильный. Когда сравнение двух элементов возвращает 0, порядок элементов непредсказуем.
%w(rhea kea flea).sort #=> ["flea", "kea", "rhea"]
(1..10).sort { |a, b| b <=> a } #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
См. также Enumerable#sort_by. Он реализует преобразование Шварца, полезное, когда вычисление или сравнение ключей дорогостоящие.
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);
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);
return ary;
} Сортирует перечисление, используя набор ключей, генерируемых путем сопоставления значений в перечислении через данный блок.
Результат не гарантируется как стабильный. Когда два ключа равны, порядок соответствующих элементов непредсказуем.
Если блок не задан, возвращается перечисление вместо этого.
%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"]
Для получения обратного порядка можно использовать следующее:
ary.sort_by { ... }.reverse! static VALUE
enum_sum(int argc, VALUE* argv, VALUE obj)
{
struct enum_sum_memo memo;
VALUE beg, end;
int excl;
memo.v = (rb_check_arity(argc, 0, 1) == 0) ? LONG2FIX(0) : argv[0];
memo.block_given = rb_block_given_p();
memo.n = 0;
memo.r = Qundef;
if ((memo.float_value = RB_FLOAT_TYPE_P(memo.v))) {
memo.f = RFLOAT_VALUE(memo.v);
memo.c = 0.0;
}
else {
memo.f = 0.0;
memo.c = 0.0;
}
if (RTEST(rb_range_values(obj, &beg, &end, &excl))) {
if (!memo.block_given && !memo.float_value &&
(FIXNUM_P(beg) || RB_TYPE_P(beg, T_BIGNUM)) &&
(FIXNUM_P(end) || RB_TYPE_P(end, T_BIGNUM))) {
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 (memo.r != Qundef) {
memo.v = rb_rational_plus(memo.r, memo.v);
}
return memo.v;
}
} Возвращает сумму элементов в Enumerable.
Если задан блок, блок применяется к каждому элементу перед сложением.
Если перечисление пустое, возвращает init.
Например:
{ 1 => 10, 2 => 20 }.sum {|k, v| k * v } #=> 50
(1..10).sum #=> 55
(1..10).sum {|v| v * 2 } #=> 110
('a'..'z').sum #=> TypeError
Этот метод может быть использован для нечисловых объектов с явным аргументом init.
{ 1 => 10, 2 => 20 }.sum([]) #=> [1, 10, 2, 20]
"a\nb\nc".each_line.lazy.map(&:chomp).sum("") #=> "abc"
Если метод применяется к диапазону Integer без блока, сумма вычисляется не итерацией, а с помощью формулы суммирования Гаусса.
Enumerable#sum метод может не учитывать переопределение метода «+» таких, как Integer#+, или методы «each», такие как Range#each.
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 элементов из перечисления.
a = [1, 2, 3, 4, 5, 0] a.take(3) #=> [1, 2, 3] a.take(30) #=> [1, 2, 3, 4, 5, 0]
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]
static VALUE
enum_tally(VALUE obj)
{
return enum_hashify(obj, 0, 0, tally_i);
} Подсчитывает элементы коллекции, т.е. считает количество вхождений каждого элемента. Возвращает словарь с элементами коллекции в качестве ключей и соответствующими счётчиками в качестве значений.
["a", "b", "c", "b"].tally #=> {"a"=>1, "b"=>2, "c"=>1}
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);
return ary;
} Возвращает массив, содержащий элементы перечисления.
(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]
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);
} Возвращает результат интерпретации перечисления как списка [key, value] пар.
%i[hello world].each_with_index.to_h
# => {:hello => 0, :world => 1}
Если задан блок, результаты блока для каждого элемента перечисления будут использоваться как пары.
(1..5).to_h {|x| [x, x ** 2]}
#=> {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
# File lib/set.rb, line 687 def to_set(klass = Set, *args, &block) klass.new(self, *args, &block) end
Создает множество из объекта перечисления с заданными аргументами. Требуется require "set" для использования этого метода.
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;
} Возвращает новый массив, удаляя дубликаты значений в self.
См. также Array#uniq.
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;
} Берет по одному элементу из перечисления и объединяет соответствующие элементы из каждого арг. Это генерирует последовательность n-элементных массивов, где n — это количество аргументов плюс 1. Длина результирующей последовательности будет 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–2020 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.