модуль Enumerable
Mixin Enumerable предоставляет классам коллекций несколько методов обхода и поиска, а также возможность сортировки. Класс должен предоставить метод each, который возвращает последовательные члены коллекции. Если используются Enumerable#max, #min, или #sort, объекты в коллекции также должны реализовывать осмысленный оператор <=>, так как эти методы полагаются на упорядочение между членами коллекции.
Методы публичного экземпляра
static VALUE
enum_all(VALUE obj)
{
NODE *memo = NEW_MEMO(Qtrue, 0, 0);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(all), (VALUE)memo);
return memo->u1.value;
} Передает каждый элемент коллекции заданному блоку. Метод возвращает true если блок никогда не возвращает false или nil. Если блок не задан, Ruby добавляет неявный блок { |obj| obj }, который заставит all? вернуть true, если ни один из членов коллекции не является false или nil.
%w[ant bear cat].all? { |word| word.length >= 3 } #=> true
%w[ant bear cat].all? { |word| word.length >= 4 } #=> false
[nil, true, 99].all? #=> false
static VALUE
enum_any(VALUE obj)
{
NODE *memo = NEW_MEMO(Qfalse, 0, 0);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(any), (VALUE)memo);
return memo->u1.value;
} Передает каждый элемент коллекции заданному блоку. Метод возвращает true если блок когда-либо возвращает значение, отличное от false или nil. Если блок не задан, Ruby добавляет неявный блок { |obj| obj }, который заставит any? вернуть true, если хотя бы один из членов коллекции не является false или nil.
%w[ant bear cat].any? { |word| word.length >= 3 } #=> true
%w[ant bear cat].any? { |word| word.length >= 4 } #=> true
[nil, true, 99].any? #=> true
static VALUE
enum_chunk(int argc, VALUE *argv, VALUE enumerable)
{
VALUE initial_state;
VALUE enumerator;
int n;
if (!rb_block_given_p())
rb_raise(rb_eArgError, "no block given");
n = rb_scan_args(argc, argv, "01", &initial_state);
if (n != 0)
rb_warn("initial_state given for chunk. (Use local variables.)");
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_ivar_set(enumerator, rb_intern("chunk_initial_state"), initial_state);
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 log можно устранить следующим образом:
sep = "-"*72 + "\n"
IO.popen("svn log README") { |f|
f.chunk { |line|
line != sep || nil
}.each { |_, lines|
pp lines
}
}
#=> ["r20018 | knu | 2008-10-29 13:20:42 +0900 (Wed, 29 Oct 2008) | 2 lines\n",
# "\n",
# "* README, README.ja: Update the portability section.\n",
# "\n"]
# ["r16725 | knu | 2008-05-31 23:34:23 +0900 (Sat, 31 May 2008) | 2 lines\n",
# "\n",
# "* README, README.ja: Add a note about default C flags.\n",
# "\n"]
# ...
Абзацы, разделённые пустыми строками, можно обработать следующим образом:
File.foreach("README").chunk { |line|
/\A\s*\z/ !~ line || nil
}.each { |_, lines|
pp lines
}
:_alone может быть использовано для принудительной группировки элементов. Например, вы можете поместить строки, содержащие URL, отдельно, а остальные строки сгруппировать вместе, как показано ниже:
pattern = /http/
open(filename) { |f|
f.chunk { |line| line =~ pattern ? :_alone : true }.each { |key, lines|
pp lines
}
}
static VALUE
enum_collect(VALUE obj)
{
VALUE ary;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, collect_i, ary);
return ary;
} Возвращает новый массив с результатами выполнения блока один раз для каждого элемента в перечислении.
Если блок не задан, возвращается итератор вместо него.
(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;
} Возвращает новый массив со склеенными результатами выполнения блока один раз для каждого элемента в перечислении.
Если блок не задан, возвращается итератор вместо него.
[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;
NODE *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 = NEW_MEMO(item, 0, 0);
rb_block_call(obj, id_each, 0, 0, func, (VALUE)memo);
return INT2NUM(memo->u3.cnt);
} Возвращает количество элементов в enum через перечисление. Если аргумент задан, подсчитывается количество элементов в enum , равных item. Если задан блок, подсчитывается количество элементов, для которых он возвращает истинное значение.
ary = [1, 2, 4, 2]
ary.count #=> 4
ary.count(2) #=> 2
ary.count{ |x| x%2==0 } #=> 3
static VALUE
enum_cycle(int argc, VALUE *argv, VALUE obj)
{
VALUE ary;
VALUE nv = Qnil;
long n, i, len;
rb_scan_args(argc, argv, "01", &nv);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_cycle_size);
if (NIL_P(nv)) {
n = -1;
}
else {
n = NUM2LONG(nv);
if (n <= 0) return Qnil;
}
ary = rb_ary_new();
RBASIC_CLEAR_CLASS(ary);
rb_block_call(obj, id_each, 0, 0, cycle_i, ary);
len = RARRAY_LEN(ary);
if (len == 0) return Qnil;
while (n < 0 || 0 < --n) {
for (i=0; i<len; i++) {
rb_yield(RARRAY_AREF(ary, i));
}
}
return Qnil;
} Вызывает блок для каждого элемента перечисления многократно n раз или бесконечно, если не указано другое или значение по умолчанию. Если задано неположительное число или коллекция пуста, ничего не происходит. Возвращает nil если цикл завершился без прерывания.
#cycle сохраняет элементы во внутреннем массиве, поэтому изменения в перечислении после первого прохода не оказывают влияния.
Если блок не задан, возвращается итератор вместо него.
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)
{
NODE *memo;
VALUE if_none;
rb_scan_args(argc, argv, "01", &if_none);
RETURN_ENUMERATOR(obj, argc, argv);
memo = NEW_MEMO(Qundef, 0, 0);
rb_block_call(obj, id_each, 0, 0, find_i, (VALUE)memo);
if (memo->u3.cnt) {
return memo->u1.value;
}
if (!NIL_P(if_none)) {
return rb_funcall(if_none, id_call, 0, 0);
}
return Qnil;
} Передает каждую запись в перечислении в блок. Возвращает первую, для которой блок не равен false. Если ни один объект не соответствует, вызывает ifnone и возвращает его результат, если он задан, или возвращает nil в противном случае.
Если блок не задан, возвращается итератор вместо него.
(1..10).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..100).find { |i| i % 5 == 0 and i % 7 == 0 } #=> 35
static VALUE
enum_drop(VALUE obj, VALUE n)
{
VALUE result;
NODE *memo;
long len = NUM2LONG(n);
if (len < 0) {
rb_raise(rb_eArgError, "attempt to drop negative size");
}
result = rb_ary_new();
memo = NEW_MEMO(result, 0, len);
rb_block_call(obj, id_each, 0, 0, drop_i, (VALUE)memo);
return result;
} Удаляет первые n элементов из перечисления и возвращает оставшиеся элементы в массиве.
a = [1, 2, 3, 4, 5, 0] a.drop(3) #=> [4, 5, 0]
static VALUE
enum_drop_while(VALUE obj)
{
VALUE result;
NODE *memo;
RETURN_ENUMERATOR(obj, 0, 0);
result = rb_ary_new();
memo = NEW_MEMO(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);
NODE *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();
memo = NEW_MEMO(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;
NODE *memo;
int arity;
if (size <= 0) rb_raise(rb_eArgError, "invalid slice size");
RETURN_SIZED_ENUMERATOR(obj, 1, &n, enum_each_slice_size);
ary = rb_ary_new2(size);
arity = rb_block_arity();
memo = NEW_MEMO(ary, dont_recycle_block_arg(arity), size);
rb_block_call(obj, id_each, 0, 0, each_slice_i, (VALUE)memo);
ary = memo->u1.value;
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)
{
NODE *memo;
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
memo = NEW_MEMO(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);
OBJ_INFECT(ary, obj);
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(int argc, VALUE *argv, VALUE obj)
{
NODE *memo;
VALUE if_none;
rb_scan_args(argc, argv, "01", &if_none);
RETURN_ENUMERATOR(obj, argc, argv);
memo = NEW_MEMO(Qundef, 0, 0);
rb_block_call(obj, id_each, 0, 0, find_i, (VALUE)memo);
if (memo->u3.cnt) {
return memo->u1.value;
}
if (!NIL_P(if_none)) {
return rb_funcall(if_none, id_call, 0, 0);
}
return Qnil;
} Передает каждый элемент в перечислителе в блок. Возвращает первый элемент, для которого блок не ложно. Если ни один объект не соответствует, вызывает ifnone и возвращает его результат, если он указан, или возвращает nil в противном случае.
Если блок не задан, вместо него возвращается перечислитель.
(1..10).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..100).find { |i| i % 5 == 0 and i % 7 == 0 } #=> 35
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)
{
NODE *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 = NEW_MEMO(Qnil, condition_value, 0);
rb_block_call(obj, id_each, 0, 0, func, (VALUE)memo);
return memo->u1.value;
} Сравнивает каждый элемент в перечислителе с value или передает в блок. Возвращает индекс первого элемента, для которого вычисленное значение не ложно. Если ни один объект не соответствует, возвращает 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)
{
NODE *memo;
rb_check_arity(argc, 0, 1);
if (argc > 0) {
return enum_take(obj, argv[0]);
}
else {
memo = NEW_MEMO(Qnil, 0, 0);
rb_block_call(obj, id_each, 0, 0, first_i, (VALUE)memo);
return memo->u1.value;
}
} Возвращает первый элемент или первые 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
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();
NODE *memo = NEW_MEMO(pat, ary, 0);
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_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)
{
NODE *memo = NEW_MEMO(val, Qfalse, 0);
rb_block_call(obj, id_each, 0, 0, member_i, (VALUE)memo);
return memo->u2.value;
} Возвращает true если любой член перечислителя равен obj. Равенство проверяется с помощью ==.
IO.constants.include? :SEEK_SET #=> true IO.constants.include? :SEEK_NO_FURTHER #=> false
static VALUE
enum_inject(int argc, VALUE *argv, VALUE obj)
{
NODE *memo;
VALUE init, op;
rb_block_call_func *iter = inject_i;
ID id;
switch (rb_scan_args(argc, argv, "02", &init, &op)) {
case 0:
init = Qundef;
break;
case 1:
if (rb_block_given_p()) {
break;
}
id = rb_check_id(&init);
op = id ? ID2SYM(id) : init;
init = Qundef;
iter = inject_op_i;
break;
case 2:
if (rb_block_given_p()) {
rb_warning("given block not used");
}
id = rb_check_id(&op);
if (id) op = ID2SYM(id);
iter = inject_op_i;
break;
}
memo = NEW_MEMO(init, Qnil, op);
rb_block_call(obj, id_each, 0, 0, iter, (VALUE)memo);
if (memo->u1.value == Qundef) return Qnil;
return memo->u1.value;
} Объединяет все элементы перечислителя, применяя бинарную операцию, заданную блоком или символом, обозначающим метод или оператор.
Если вы задаёте блок, то для каждого элемента в перечислителе блоку передаётся значение аккумулятора (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, 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;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, collect_i, ary);
return ary;
} Возвращает новый массив с результатами выполнения блока один раз для каждого элемента в перечислителе.
Если блок не задан, вместо этого возвращается перечислитель.
(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)
{
NODE *memo = NEW_MEMO(Qundef, 0, 0);
VALUE result;
VALUE num;
rb_scan_args(argc, argv, "01", &num);
if (!NIL_P(num))
return nmin_run(obj, num, 0, 1);
if (rb_block_given_p()) {
rb_block_call(obj, id_each, 0, 0, max_ii, (VALUE)memo);
}
else {
rb_block_call(obj, id_each, 0, 0, max_i, (VALUE)memo);
}
result = memo->u1.value;
if (result == Qundef) return Qnil;
return result;
} Возвращает объект в перечислителе с максимальным значением. Первая форма предполагает, что все объекты реализуют 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)
{
NODE *memo;
VALUE num;
rb_scan_args(argc, argv, "01", &num);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
if (!NIL_P(num))
return nmin_run(obj, num, 1, 1);
memo = NEW_MEMO(Qundef, Qnil, 0);
rb_block_call(obj, id_each, 0, 0, max_by_i, (VALUE)memo);
return memo->u2.value;
} Возвращает объект в перечислении, который даёт максимальное значение от заданного блока.
Если блок не задан, возвращается перечислитель.
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)
{
NODE *memo = NEW_MEMO(val, Qfalse, 0);
rb_block_call(obj, id_each, 0, 0, member_i, (VALUE)memo);
return memo->u2.value;
} Возвращает true , если любой член перечисления равен объекту. Равенство проверяется с помощью ==.
IO.constants.include? :SEEK_SET #=> true IO.constants.include? :SEEK_NO_FURTHER #=> false
static VALUE
enum_min(int argc, VALUE *argv, VALUE obj)
{
NODE *memo = NEW_MEMO(Qundef, 0, 0);
VALUE result;
VALUE num;
rb_scan_args(argc, argv, "01", &num);
if (!NIL_P(num))
return nmin_run(obj, num, 0, 0);
if (rb_block_given_p()) {
rb_block_call(obj, id_each, 0, 0, min_ii, (VALUE)memo);
}
else {
rb_block_call(obj, id_each, 0, 0, min_i, (VALUE)memo);
}
result = memo->u1.value;
if (result == Qundef) return Qnil;
return result;
} Возвращает объект в перечислении с минимальным значением. Первый вариант предполагает, что все объекты реализуют 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)
{
NODE *memo;
VALUE num;
rb_scan_args(argc, argv, "01", &num);
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
if (!NIL_P(num))
return nmin_run(obj, num, 1, 0);
memo = NEW_MEMO(Qundef, Qnil, 0);
rb_block_call(obj, id_each, 0, 0, min_by_i, (VALUE)memo);
return memo->u2.value;
} Возвращает объект в перечислении, который даёт минимальное значение от заданного блока.
Если блок не задан, возвращается перечислитель.
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)
{
NODE *memo = NEW_MEMO(Qundef, Qundef, Qundef);
struct minmax_t *m = (struct minmax_t *)&memo->u1.value;
VALUE ary = rb_ary_new3(2, Qnil, Qnil);
m->min = Qundef;
m->last = Qundef;
if (rb_block_given_p()) {
rb_block_call(obj, id_each, 0, 0, minmax_ii, (VALUE)memo);
if (m->last != Qundef)
minmax_ii_update(m->last, m->last, m);
}
else {
rb_block_call(obj, id_each, 0, 0, minmax_i, (VALUE)memo);
if (m->last != Qundef)
minmax_i_update(m->last, m->last, m);
}
if (m->min != Qundef) {
rb_ary_store(ary, 0, m->min);
rb_ary_store(ary, 1, m->max);
}
return ary;
} Возвращает массив из двух элементов, содержащий минимальное и максимальное значения в перечислении. Первый вариант предполагает, что все объекты реализуют 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);
} Возвращает массив из двух элементов, содержащий объекты в перечислении, которые соответствуют минимальному и максимальному значениям соответственно из заданного блока.
Если блок не задан, возвращается перечислитель.
a = %w(albatross dog horse)
a.minmax_by { |x| x.length } #=> ["dog", "albatross"]
static VALUE
enum_none(VALUE obj)
{
NODE *memo = NEW_MEMO(Qtrue, 0, 0);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(none), (VALUE)memo);
return memo->u1.value;
} Передаёт каждый элемент коллекции заданному блоку. Метод возвращает true , если блок никогда не возвращает true для всех элементов. Если блок не задан, none? вернёт true только если ни один из членов коллекции не является истинным.
%w{ant bear cat}.none? { |word| word.length == 5 } #=> true
%w{ant bear cat}.none? { |word| word.length >= 4 } #=> false
[].none? #=> true
[nil].none? #=> true
[nil, false].none? #=> true
static VALUE
enum_one(VALUE obj)
{
NODE *memo = NEW_MEMO(Qundef, 0, 0);
VALUE result;
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(one), (VALUE)memo);
result = memo->u1.value;
if (result == Qundef) return Qfalse;
return result;
} Передаёт каждый элемент коллекции заданному блоку. Метод возвращает true , если блок возвращает true ровно один раз. Если блок не задан, one? вернёт true только если ровно один из членов коллекции является истинным.
%w{ant bear cat}.one? { |word| word.length == 4 } #=> true
%w{ant bear cat}.one? { |word| word.length > 4 } #=> false
%w{ant bear cat}.one? { |word| word.length < 4 } #=> false
[ nil, true, 99 ].one? #=> false
[ nil, true, false ].one? #=> true
static VALUE
enum_partition(VALUE obj)
{
NODE *memo;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
memo = NEW_MEMO(rb_ary_new(), rb_ary_new(), 0);
rb_block_call(obj, id_each, 0, 0, partition_i, (VALUE)memo);
return rb_assoc_new(memo->u1.value, memo->u2.value);
} Возвращает два массива: первый содержит элементы перечисления, для которых блок возвращает true, второй содержит остальные.
Если блок не задан, возвращается перечислитель.
(1..6).partition { |v| v.even? } #=> [[2, 4, 6], [1, 3, 5]]
static VALUE
enum_inject(int argc, VALUE *argv, VALUE obj)
{
NODE *memo;
VALUE init, op;
rb_block_call_func *iter = inject_i;
ID id;
switch (rb_scan_args(argc, argv, "02", &init, &op)) {
case 0:
init = Qundef;
break;
case 1:
if (rb_block_given_p()) {
break;
}
id = rb_check_id(&init);
op = id ? ID2SYM(id) : init;
init = Qundef;
iter = inject_op_i;
break;
case 2:
if (rb_block_given_p()) {
rb_warning("given block not used");
}
id = rb_check_id(&op);
if (id) op = ID2SYM(id);
iter = inject_op_i;
break;
}
memo = NEW_MEMO(init, Qnil, op);
rb_block_call(obj, id_each, 0, 0, iter, (VALUE)memo);
if (memo->u1.value == Qundef) return Qnil;
return memo->u1.value;
} Объединяет все элементы перечисления, применяя бинарную операцию, заданную блоком или символом, который называет метод или оператор.
Если задан блок, то для каждого элемента в перечислении блоку передаётся значение аккумулятора (мемо) и элемент. Если вместо этого задан символ, то каждый элемент в коллекции будет передан в метод мемо. В обоих случаях результат становится новым значением для мемо. В конце итерации окончательное значение мемо является возвращаемым значением метода.
Если вы не укажете явное начальное значение для мемо, то первый элемент коллекции используется в качестве начального значения мемо.
# 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 pattan 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()) {
VALUE initial_state;
int n;
n = rb_scan_args(argc, argv, "01", &initial_state);
if (n != 0)
rb_warn("initial_state given for slice_before. (Use local variables.)");
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, rb_intern("slicebefore_sep_pred"), rb_block_proc());
rb_ivar_set(enumerator, rb_intern("slicebefore_initial_state"), initial_state);
}
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, такие как map, также могут быть использованы.
Например, итерация по записям ChangeLog может быть реализована следующим образом:
# iterate over ChangeLog entries.
open("ChangeLog") { |f|
f.slice_before(/\A\S/).each { |e| pp e }
}
# same as above. block is used instead of pattern argument.
open("ChangeLog") { |f|
f.slice_before { |line| /\A\S/ === line }.each { |e| pp e }
}
«svn proplist -R» производит многострочный вывод для каждого файла. Они могут быть разбиты на блоки следующим образом:
IO.popen([{"LC_ALL"=>"C"}, "svn", "proplist", "-R"]) { |f|
f.lines.slice_before(/\AProp/).each { |lines| p lines }
}
#=> ["Properties on '.':\n", " svn:ignore\n", " svk:merge\n"]
# ["Properties on 'goruby.c':\n", " svn:eol-style\n"]
# ["Properties on 'complex.c':\n", " svn:mime-type\n", " svn:eol-style\n"]
# ["Properties on 'regparse.c':\n", " svn:eol-style\n"]
# ...
Если блоку необходимо сохранять состояние для нескольких элементов, можно использовать локальные переменные. Например, три или более последовательных возрастающих числа можно объединить следующим образом:
a = [0, 2, 3, 4, 6, 7, 9]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map { |es|
es.length <= 2 ? es.join(",") : "#{es.first}-#{es.last}"
}.join(",")
#=> "0,2-4,6,7,9"
Однако локальные переменные следует использовать осторожно, если перечислитель результатов перебирается дважды или более. Локальные переменные должны быть инициализированы для каждой итерации. Можно использовать Enumerator.new для этого.
# Word wrapping. This assumes all characters have same width.
def wordwrap(words, maxwidth)
Enumerator.new {|y|
# cols is initialized in Enumerator.new.
cols = 0
words.slice_before { |w|
cols += 1 if cols != 0
cols += w.length
if maxwidth < cols
cols = w.length
true
else
false
end
}.each {|ws| y.yield ws }
}
end
text = (1..20).to_a.join(" ")
enum = wordwrap(text.split(/\s+/), 10)
puts "-"*10
enum.each { |ws| puts ws.join(" ") } # first enumeration.
puts "-"*10
enum.each { |ws| puts ws.join(" ") } # second enumeration generates same result as the first.
puts "-"*10
#=> ----------
# 1 2 3 4 5
# 6 7 8 9 10
# 11 12 13
# 14 15 16
# 17 18 19
# 20
# ----------
# 1 2 3 4 5
# 6 7 8 9 10
# 11 12 13
# 14 15 16
# 17 18 19
# 20
# ----------
mbox содержит серию писем, которые начинаются с строки Unix From. Таким образом, каждое письмо может быть извлечено, используя 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|
f.slice_before(emp: true) { |line, h|
prevemp = h[:emp]
h[: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_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"]]
static VALUE
enum_sort(VALUE obj)
{
return rb_ary_sort(enum_to_a(0, 0, obj));
} Возвращает массив, содержащий элементы enum в отсортированном виде, либо согласно их собственному методу <=>, либо с использованием результатов предоставленного блока. Блок должен возвращать -1, 0 или +1 в зависимости от сравнения a и b. Начиная с Ruby 1.8, метод Enumerable#sort_by реализует встроенную трансформу Шварца, которая полезна, когда вычисление ключа или сравнение являются дорогостоящими операциями.
%w(rhea kea flea).sort #=> ["flea", "kea", "rhea"]
(1..10).sort { |a, b| b <=> a } #=> [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
static VALUE
enum_sort_by(VALUE obj)
{
VALUE ary, buf;
NODE *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 = NEW_MEMO(0, 0, 0);
OBJ_INFECT(memo, obj);
data = (struct sort_by_data *)&memo->u1;
data->ary = ary;
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_take(VALUE obj, VALUE n)
{
NODE *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 = NEW_MEMO(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] пар.
%[hello world].each_with_index.to_h
# => {:hello => 0, :world => 1}
# File lib/set.rb, line 690 def to_set(klass = Set, *args, &block) klass.new(self, *args, &block) end
Создаёт множество из объекта перечислителя с заданными аргументами. Для использования этого метода необходимо добавить +require “set”+.
static VALUE
enum_zip(int argc, VALUE *argv, VALUE obj)
{
int i;
ID conv;
NODE *memo;
VALUE result = Qnil;
VALUE args = rb_ary_new4(argc, argv);
int allary = TRUE;
argv = RARRAY_PTR(args);
for (i=0; i<argc; i++) {
VALUE ary = rb_check_array_type(argv[i]);
if (NIL_P(ary)) {
allary = FALSE;
break;
}
argv[i] = ary;
}
if (!allary) {
CONST_ID(conv, "to_enum");
for (i=0; i<argc; i++) {
if (!rb_respond_to(argv[i], id_each)) {
rb_raise(rb_eTypeError, "wrong argument type %s (must respond to :each)",
rb_obj_classname(argv[i]));
}
argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each));
}
}
if (!rb_block_given_p()) {
result = rb_ary_new();
}
/* use NODE_DOT2 as memo(v, v, -) */
memo = rb_node_newnode(NODE_DOT2, 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]]
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.