модуль 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.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]]
Enumerable#slice_when делает то же самое, за исключением разделения, когда блок возвращает true вместо false.
static VALUE
enum_collect(VALUE obj)
{
VALUE ary;
int min_argc, max_argc;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
min_argc = rb_block_min_max_arity(&max_argc);
rb_lambda_call(obj, id_each, 0, 0, collect_i, min_argc, max_argc, ary);
return ary;
} Возвращает новый массив с результатами однократного выполнения блока для каждого элемента в 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;
} Возвращает новый массив с объединенными результатами однократного выполнения блока для каждого элемента в 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_check_arity(argc, 0, 1);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_cycle_size);
if (!argc || NIL_P(nv = argv[0])) {
n = -1;
}
else {
n = NUM2LONG(nv);
if (n <= 0) return Qnil;
}
ary = rb_ary_new();
RBASIC_CLEAR_CLASS(ary);
rb_block_call(obj, id_each, 0, 0, cycle_i, ary);
len = RARRAY_LEN(ary);
if (len == 0) return Qnil;
while (n < 0 || 0 < --n) {
for (i=0; i<len; i++) {
enum_yield_array(RARRAY_AREF(ary, i));
}
}
return Qnil;
} Вызывает блок для каждого элемента enum многократно n раз или бесконечно, если не задано значение или задано nil. Если задано неположительное число или коллекция пуста, ничего не делает. Возвращает nil, если цикл завершился без прерывания.
Enumerable#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;
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;
} Передает каждую запись в enum в блок. Возвращает первую, для которой блок не является ложным. Если ни один объект не соответствует, вызывает ifnone и возвращает его результат, если он указан, или возвращает 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_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;
} Вызывает блок один раз для каждого элемента в 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]
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_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 возвращает истинное значение.
Методы 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;
} Передает каждый элемент в перечислении в блок. Возвращает первый, для которого блок не ложно. Если ни один объект не соответствует, вызывается если_нет и возвращается его результат, если он указан, или возвращается 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 возвращает истинное значение.
Методы 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;
} Сравнивает каждый элемент в перечислении со значением или передает в блок. Возвращает индекс первого, для которого вычисленное значение не ложно. Если ни один объект не соответствует, возвращает 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)
{
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;
} Возвращает массив каждого элемента в перечислении, для которого 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)
{
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;
} Инвертированная версия 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]}
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. Равенство проверяется с помощью ==.
(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 является возвращаемым значением метода.
Если вы явно не указываете начальное значение для 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, PASS_KW_SPLAT);
/* 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
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;
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 с максимальным значением. Первая форма предполагает, что все объекты реализуют 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_check_arity(argc, 0, 1);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
if (argc && !NIL_P(num = argv[0]))
return rb_nmin_run(obj, num, 1, 1, 0);
memo = MEMO_NEW(Qundef, Qnil, 0);
rb_block_call(obj, id_each, 0, 0, max_by_i, (VALUE)memo);
return memo->v2;
} Возвращает объект в 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. Равенство проверяется с помощью ==.
(1..10).include? 5 #=> true (1..10).include? 15 #=> false (1..10).member? 5 #=> true (1..10).member? 15 #=> 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;
if (rb_check_arity(argc, 0, 1) && !NIL_P(num = argv[0]))
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_check_arity(argc, 0, 1);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
if (argc && !NIL_P(num = argv[0]))
return rb_nmin_run(obj, num, 1, 0, 0);
memo = MEMO_NEW(Qundef, Qnil, 0);
rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo);
return memo->v2;
} Возвращает объект в 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);
WARN_UNUSED_BLOCK(argc);
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;
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]]
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.
Если блок не задан, вместо него возвращается 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
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_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 перед строкой 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]]
Абзацы (непустые строки с последующими пустыми строками) могут быть разбиты на блоки следующим образом: (см. 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) {
/* 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.
Если задан блок, блок применяется к каждому элементу перед добавлением.
Если перечисление пусто, возвращается 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 805 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 - на единицу больше количества аргументов. Длина результирующей последовательности будет 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.