модуль 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);
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);
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_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.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`.
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]]
#slice_when делает то же самое, за исключением разделения, когда блок возвращает true вместо false.
static VALUE
enum_collect(VALUE obj)
{
VALUE ary;
int min_argc, max_argc;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
min_argc = rb_block_min_max_arity(&max_argc);
rb_lambda_call(obj, id_each, 0, 0, collect_i, min_argc, max_argc, ary);
return ary;
} Возвращает новый массив с результатами однократного выполнения block для каждого элемента в enum.
Если блок не задан, вместо этого возвращается перечислитель.
(1..4).map { |i| i*i } #=> [1, 4, 9, 16]
(1..4).collect { "cat" } #=> ["cat", "cat", "cat", "cat"]
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]
static VALUE
enum_count(int argc, VALUE *argv, VALUE obj)
{
VALUE item = Qnil;
struct MEMO *memo;
rb_block_call_func *func;
if (argc == 0) {
if (rb_block_given_p()) {
func = count_iter_i;
}
else {
func = count_all_i;
}
}
else {
rb_scan_args(argc, argv, "1", &item);
if (rb_block_given_p()) {
rb_warn("given block not used");
}
func = count_i;
}
memo = MEMO_NEW(item, 0, 0);
rb_block_call(obj, id_each, 0, 0, func, (VALUE)memo);
return imemo_count_value(memo);
} Возвращает количество элементов в enum с помощью перечисления. Если задан аргумент, подсчитывается количество элементов в enum, которые равны item. Если задан блок, он подсчитывает количество элементов, дающих истинное значение.
ary = [1, 2, 4, 2]
ary.count #=> 4
ary.count(2) #=> 2
ary.count{ |x| x%2==0 } #=> 3
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++) {
enum_yield_array(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.
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..100).detect => #<Enumerator: 1..100:detect>
(1..100).find => #<Enumerator: 1..100:find>
(1..10).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..10).find { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..100).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> 35
(1..100).find { |i| i % 5 == 0 and i % 7 == 0 } #=> 35 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]
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]
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;
} Вызывает 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
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;
} Вызывает 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}
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]
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]
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 не равен false. Если ни один объект не соответствует условию, вызывается ifnone и возвращает его результат, если он указан, иначе возвращает nil.
Если блок не указан, возвращается перечислитель.
(1..100).detect => #<Enumerator: 1..100:detect>
(1..100).find => #<Enumerator: 1..100:find>
(1..10).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..10).find { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..100).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> 35
(1..100).find { |i| i % 5 == 0 and 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.
Если блок не указан, вместо него возвращается Enumerator.
(1..10).find_all { |i| i % 3 == 0 } #=> [3, 6, 9]
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
См. также #reject.
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. Возвращает индекс первого элемента, для которого вычисленное значение не равно false. Если ни один объект не соответствует, возвращает 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
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;
} Возвращает новый массив с объединёнными результатами выполнения 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]
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]
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]
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]}
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
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 является возвращаемым значением метода.
Если вы не указываете явное начальное значение для 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);
/* 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
static VALUE
enum_collect(VALUE obj)
{
VALUE ary;
int min_argc, max_argc;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
min_argc = rb_block_min_max_arity(&max_argc);
rb_lambda_call(obj, id_each, 0, 0, collect_i, min_argc, max_argc, ary);
return ary;
} Возвращает новый массив с результатами выполнения 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;
rb_scan_args(argc, argv, "01", &num);
if (!NIL_P(num))
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 с максимальным значением. Первая форма предполагает, что все объекты реализуют 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]
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 rb_nmin_run(obj, num, 1, 1, 0);
memo = MEMO_NEW(Qundef, Qnil, 0);
rb_block_call(obj, id_each, 0, 0, max_by_i, (VALUE)memo);
return memo->v2;
} Возвращает объект в 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] }
#=> *
# ***
# ******
# ***********
# ******************
# *****************************
# *****************************************
# ****************************************************
# ***************************************************************
# ********************************************************************
# ***********************************************************************
# ***********************************************************************
# **************************************************************
# ****************************************************
# ***************************************
# ***************************
# ******************
# ***********
# *******
# ***
# *
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
static VALUE
enum_min(int argc, VALUE *argv, VALUE obj)
{
VALUE memo;
struct min_t *m = NEW_CMP_OPT_MEMO(struct min_t, memo);
VALUE result;
VALUE num;
rb_scan_args(argc, argv, "01", &num);
if (!NIL_P(num))
return rb_nmin_run(obj, num, 0, 0, 0);
m->min = 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, min_ii, memo);
}
else {
rb_block_call(obj, id_each, 0, 0, min_i, memo);
}
result = m->min;
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]
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 rb_nmin_run(obj, num, 1, 0, 0);
memo = MEMO_NEW(Qundef, Qnil, 0);
rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo);
return memo->v2;
} Возвращает объект в 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"]
static VALUE
enum_minmax(VALUE obj)
{
VALUE memo;
struct minmax_t *m = NEW_CMP_OPT_MEMO(struct minmax_t, memo);
m->min = Qundef;
m->last = 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, minmax_ii, memo);
if (m->last != Qundef)
minmax_ii_update(m->last, m->last, m);
}
else {
rb_block_call(obj, id_each, 0, 0, minmax_i, 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"]
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"]
static VALUE
enum_none(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(none), (VALUE)memo);
return memo->v1;
} Передает каждый элемент коллекции в заданный блок. Метод возвращает true, если блок никогда не возвращает true для всех элементов. Если блок не задан, none? вернет true, только если ни один из членов коллекции не является истинным.
Если вместо этого задан шаблон, метод возвращает, соответствует ли pattern
=== element ни одному из членов коллекции.
%w{ant bear cat}.none? { |word| word.length == 5 } #=> true
%w{ant bear cat}.none? { |word| word.length >= 4 } #=> false
%w{ant bear cat}.none?(/d/) #=> true
[1, 3.14, 42].none?(Float) #=> false
[].none? #=> true
[nil].none? #=> true
[nil, false].none? #=> true
[nil, false, true].none? #=> false
static VALUE
enum_one(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qundef);
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, только если ровно один из членов коллекции является истинным.
Если вместо этого задан шаблон, метод возвращает, соответствует ли 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]]
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 является возвращаемым значением для метода.
Если вы не указываете явно начальное значение для 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.
Если блок не задан, вместо этого возвращается перечислитель.
(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.
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 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.
Если блок не задан, вместо этого возвращается перечислитель.
(1..10).find_all { |i| i % 3 == 0 } #=> [3, 6, 9]
[1,2,3,4,5].select { |num| num.even? } #=> [2, 4]
См. также #reject.
static VALUE
enum_slice_after(int argc, VALUE *argv, VALUE enumerable)
{
VALUE enumerator;
VALUE pat = Qnil, pred = Qnil;
if (rb_block_given_p()) {
if (0 < argc)
rb_raise(rb_eArgError, "both pattern and block are given");
pred = rb_block_proc();
}
else {
rb_scan_args(argc, argv, "1", &pat);
}
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, 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 для элемента, то элемент является концом фрагмента.
Перечислитель и block вызываются от первого элемента к последнему элементу 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 для элемента, то элемент является началом фрагмента.
Перечислитель и block вызываются от первого элемента к последнему элементу 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.
# 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]]
Абзацы (непустые строки с последующими пустыми строками) можно разбить на фрагменты следующим образом: (См. #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"]]
#chunk_while делает то же самое, за исключением разделения, когда блок возвращает false вместо true.
static VALUE
enum_sort(VALUE obj)
{
return rb_ary_sort_bang(enum_to_a(0, 0, obj));
} Возвращает массив, содержащий элементы enum, отсортированные.
Сравнения для сортировки будут выполняться с использованием собственного оператора элементов или с использованием необязательного блока кода.
Блок должен реализовывать сравнение между 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]
См. также #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);
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"]
static VALUE
enum_sum(int argc, VALUE* argv, VALUE obj)
{
struct enum_sum_memo memo;
VALUE beg, end;
int excl;
if (rb_scan_args(argc, argv, "01", &memo.v) == 0)
memo.v = LONG2FIX(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;
}
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) {
/* r can be an Integer when mathn is loaded */
if (FIXNUM_P(memo.r))
memo.v = rb_fix_plus(memo.r, memo.v);
else if (RB_TYPE_P(memo.r, T_BIGNUM))
memo.v = rb_big_plus(memo.r, memo.v);
else
memo.v = rb_rational_plus(memo.r, memo.v);
}
return memo.v;
}
} Возвращает сумму элементов в Enumerable.
Если задан блок, блок применяется к каждому элементу перед сложением.
Если enum пуст, возвращает init.
Например:
{ 1 => 10, 2 => 20 }.sum {|k, v| k * v } #=> 50
(1..10).sum #=> 55
(1..10).sum {|v| v * 2 } #=> 110
[Object.new].each.sum #=> TypeError
Этот метод может быть использован для нечисловых объектов с явным аргументом init.
{ 1 => 10, 2 => 20 }.sum([]) #=> [1, 10, 2, 20]
"a\nb\nc".each_line.lazy.map(&:chomp).sum("") #=> "abc"
#sum метод может не учитывать переопределение метода “+” таких методов, как Integer#+.
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]
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_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]
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] пар.
%i[hello world].each_with_index.to_h
# => {:hello => 0, :world => 1}
# File lib/set.rb, line 811 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;
} Берёт один элемент из 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.