модуль Enumerable
Что здесь
Модуль Enumerable предоставляет методы, полезные для класса коллекций для:
Методы для запроса
Эти методы возвращают информацию об Enumerable, помимо самих элементов:
-
include?,member? -
Возвращает
trueесли self == object,falseв противном случае. -
all? -
Возвращает
trueесли все элементы удовлетворяют заданному критерию;falseв противном случае. -
any? -
Возвращает
trueесли хотя бы один элемент удовлетворяет заданному критерию;falseв противном случае. -
none? -
Возвращает
trueесли ни один элемент не удовлетворяет заданному критерию;falseв противном случае. -
one? -
Возвращает
trueесли ровно один элемент удовлетворяет заданному критерию;falseв противном случае. -
count -
Возвращает количество элементов, исходя из аргумента или блока критерия, если он задан.
-
tally -
Возвращает новую Hash, содержащую количество вхождений каждого элемента.
Методы для извлечения
Эти методы возвращают записи из Enumerable, не изменяя его:
Начальные, конечные или все элементы:
-
entries,to_a -
Возвращает все элементы.
-
first -
Возвращает первый элемент или начальные элементы.
-
take -
Возвращает заданное число начальных элементов.
-
drop -
Возвращает заданное число конечных элементов.
-
take_while -
Возвращает начальные элементы, как указано заданным блоком.
-
drop_while -
Возвращает конечные элементы, как указано заданным блоком.
Минимальное и максимальное значение элементов:
-
min -
Возвращает элементы с наименьшими значениями среди элементов, как определено
<=>или заданным блоком. -
max -
Возвращает элементы с наибольшими значениями среди элементов, как определено
<=>или заданным блоком. -
minmax -
Возвращает массив из 2 элементов, содержащий наименьший и наибольший элементы.
-
min_by -
Возвращает наименьший элемент, как определено заданным блоком.
-
max_by -
Возвращает наибольший элемент, как определено заданным блоком.
-
minmax_by -
Возвращает наименьший и наибольший элементы, как определено заданным блоком.
Группы, срезы и разделения:
-
group_by -
Возвращает Hash, который разделяет элементы на группы.
-
partition -
Возвращает элементы, разделенные на два новых массива, как определено заданным блоком.
-
slice_after -
Возвращает новый Enumerator, записи которого представляют собой разбиение
self, основанное либо на заданномobjectили заданном блоке. -
slice_before -
Возвращает новый Enumerator, записи которого представляют собой разбиение
self, основанное либо на заданномobjectили заданном блоке. -
slice_when -
Возвращает новый Enumerator, записи которого представляют собой разбиение
selfна основе заданного блока. -
chunk -
Возвращает элементы, организованные в блоки, как указано в заданном блоке.
-
chunk_while -
Возвращает элементы, организованные в блоки, как указано в заданном блоке.
Методы для поиска и фильтрации
Эти методы возвращают элементы, которые соответствуют заданному критерию.
-
find,detect -
Возвращает элемент, выбранный блоком.
-
find_all,filter,select -
Возвращает элементы, выбранные блоком.
-
find_index -
Возвращает индекс элемента, выбранного заданным объектом или блоком.
-
reject -
Возвращает элементы, не отклоненные блоком.
-
uniq -
Возвращает элементы, которые не являются дубликатами.
Методы для сортировки
Эти методы возвращают элементы в отсортированном порядке.
-
sort -
Возвращает элементы, отсортированные по
<=>или заданному блоку. -
sort_by -
Возвращает элементы, отсортированные по заданному блоку.
Методы для итерации
-
each_entry -
Вызывает блок с каждым последующим элементом (несколько отличается от each).
-
each_with_index -
Вызывает блок с каждым последующим элементом и его индексом.
-
each_with_object -
Вызывает блок с каждым последующим элементом и заданным объектом.
-
each_slice -
Вызывает блок с последовательными непересекающимися срезами.
-
each_cons -
Вызывает блок с последовательными перекрывающимися срезами. (отличается от
each_slice). -
reverse_each -
Вызывает блок с каждым последующим элементом в обратном порядке.
Другие методы
-
map,collect -
Возвращает объекты, возвращаемые блоком.
-
filter_map -
Возвращает истинные объекты, возвращаемые блоком.
-
flat_map,collect_concat -
Возвращает уплощенные объекты, возвращаемые блоком.
-
grep -
Возвращает элементы, выбранные заданным объектом или объектами, возвращаемыми заданным блоком.
-
grep_v -
Возвращает элементы, выбранные заданным объектом или объектами, возвращаемыми заданным блоком.
-
reduce,inject -
Возвращает объект, образованный путем объединения всех элементов.
-
sum -
Возвращает сумму элементов, используя метод +++.
-
zip -
Объединяет каждый элемент с элементами других перечислителей; возвращает n-кортежи или вызывает блок с каждым.
-
cycle -
Вызывает блок с каждым элементом, циклически повторяясь.
Использование
Чтобы использовать модуль Enumerable в классе коллекции:
-
Включите его:
include Enumerable
-
Реализуйте метод
#each, который должен возвращать последовательные элементы коллекции. Этот метод будет вызываться почти любым методом Enumerable.
Пример:
class Foo
include Enumerable
def each
yield 1
yield 1, 2
yield
end
end
Foo.new.each_entry{ |element| p element }
Вывод:
1 [1, 2] nil
Enumerable в классах ядра Ruby
В некоторых классах Ruby содержится Enumerable:
Практически все методы в Enumerable вызывают метод #each в включающем классе:
-
Hash#eachвозвращает следующую пару ключ-значение в виде массива из 2 элементов. -
Struct#eachвозвращает следующую пару имя-значение в виде массива из 2 элементов. -
Для других классов выше,
#eachвозвращает следующий объект из коллекции.
О примерах
Примеры кода для методов Enumerable:
-
Всегда демонстрируют использование одного или нескольких классов, подобных массиву (часто сам массив).
-
Иногда демонстрируют использование класса, подобного хэшу. Однако для некоторых методов такое использование не имеет смысла, и поэтому оно не показано. Пример:
tallyнайдёт ровно по одному экземпляру каждого элемента хэша.
Методы публичного экземпляра
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;
} Возвращает, соответствуют ли все элементы заданному критерию.
Без аргумента и без блока возвращает, являются ли все элементы истинными:
(1..4).all? # => true %w[a b c d].all? # => true [1, 2, nil].all? # => false ['a','b', false].all? # => false [].all? # => true
С аргументом pattern и без блока, возвращает, верно ли, что для каждого элемента element, pattern === element:
(1..4).all?(Integer) # => true
(1..4).all?(Numeric) # => true
(1..4).all?(Float) # => false
%w[bar baz bat bam].all?(/ba/) # => true
%w[bar baz bat bam].all?(/bar/) # => false
%w[bar baz bat bam].all?('ba') # => false
{foo: 0, bar: 1, baz: 2}.all?(Array) # => true
{foo: 0, bar: 1, baz: 2}.all?(Hash) # => false
[].all?(Integer) # => true
С заданным блоком, возвращает, возвращает ли блок истинное значение для каждого элемента:
(1..4).all? {|element| element < 5 } # => true
(1..4).all? {|element| element < 4 } # => false
{foo: 0, bar: 1, baz: 2}.all? {|key, value| value < 3 } # => true
{foo: 0, bar: 1, baz: 2}.all? {|key, value| value < 2 } # => false
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;
} Возвращает, соответствует ли хоть один элемент заданному критерию.
Без аргумента и без блока возвращает, является ли хоть один элемент истинным:
(1..4).any? # => true %w[a b c d].any? # => true [1, false, nil].any? # => true [].any? # => false
С аргументом pattern и без блока, возвращает, верно ли, что для любого элемента element, pattern === element:
[nil, false, 0].any?(Integer) # => true
[nil, false, 0].any?(Numeric) # => true
[nil, false, 0].any?(Float) # => false
%w[bar baz bat bam].any?(/m/) # => true
%w[bar baz bat bam].any?(/foo/) # => false
%w[bar baz bat bam].any?('ba') # => false
{foo: 0, bar: 1, baz: 2}.any?(Array) # => true
{foo: 0, bar: 1, baz: 2}.any?(Hash) # => false
[].any?(Integer) # => false
С заданным блоком, возвращает, возвращает ли блок истинное значение для любого элемента:
(1..4).any? {|element| element < 2 } # => true
(1..4).any? {|element| element < 1 } # => false
{foo: 0, bar: 1, baz: 2}.any? {|key, value| value < 1 } # => true
{foo: 0, bar: 1, baz: 2}.any? {|key, value| value < 0 } # => 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 new_enum_chain(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, id_chunk_enumerable, enumerable);
rb_ivar_set(enumerator, id_chunk_categorize, rb_block_proc());
rb_block_call(enumerator, idInitialize, 0, 0, chunk_i, enumerator);
return enumerator;
} Каждый элемент возвращаемого перечислителя — это массив из 2 элементов, состоящий из:
-
Значения, возвращенного блоком.
-
Массива («группа») содержащего элемент, для которого было возвращено это значение, и все последующие элементы, для которых блок вернул то же значение:
Так что:
-
Каждое значение возвращаемое блоком, отличное от предыдущего, начинает новую группу.
-
Каждое значение возвращаемое блоком, такое же, как предыдущее, продолжает ту же группу.
Пример:
e = (0..10).chunk {|i| (i / 3).floor } # => #<Enumerator: ...>
# The enumerator elements.
e.next # => [0, [0, 1, 2]]
e.next # => [1, [3, 4, 5]]
e.next # => [2, [6, 7, 8]]
e.next # => [3, [9, 10]]
Метод chunk особенно полезен для перечислителя, который уже отсортирован. В этом примере подсчитывается количество слов для каждой начальной буквы в большом массиве слов:
# Get sorted words from a web page.
url = 'https://raw.githubusercontent.com/eneko/data-repository/master/data/words.txt'
words = URI::open(url).readlines
# Make chunks, one for each letter.
e = words.chunk {|word| word.upcase[0] } # => #<Enumerator: ...>
# Display 'A' through 'F'.
e.each {|c, words| p [c, words.length]; break if c == 'F' }
Вывод:
["A", 17096] ["B", 11070] ["C", 19901] ["D", 10896] ["E", 8736] ["F", 6860]
Вы можете использовать специальный символ :_alone для принудительного помещения элемента в отдельную группу:
a = [0, 0, 1, 1]
e = a.chunk{|i| i.even? ? :_alone : true }
e.to_a # => [[:_alone, [0]], [:_alone, [0]], [true, [1, 1]]]
Например, вы можете поместить каждую строку, содержащую URL, в свою собственную группу:
pattern = /http/
open(filename) { |f|
f.chunk { |line| line =~ pattern ? :_alone : true }.each { |key, lines|
pp lines
}
}
Вы можете использовать специальный символ :_separator или nil для принудительного игнорирования элемента (не включение в какую-либо группу):
a = [0, 0, -1, 1, 1]
e = a.chunk{|i| i < 0 ? :_separator : true }
e.to_a # => [[true, [0, 0]], [true, [1, 1]]]
Обратите внимание, что разделитель не заканчивает группу:
a = [0, 0, -1, 1, -1, 1]
e = a.chunk{|i| i < 0 ? :_separator : true }
e.to_a # => [[true, [0, 0]], [true, [1]], [true, [1]]]
Например, последовательность дефисов в журнале 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
}
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, id_slicewhen_enum, enumerable);
rb_ivar_set(enumerator, id_slicewhen_pred, pred);
rb_ivar_set(enumerator, id_slicewhen_inverted, Qtrue);
rb_block_call(enumerator, idInitialize, 0, 0, slicewhen_i, enumerator);
return enumerator;
} Возвращаемый Enumerator использует блок для разбиения элементов на массивы («группы»); вызывает блок с каждым элементом и его преемником; начинает новую группу только в том случае, если блок возвращает истинное значение:
Пример:
a = [1, 2, 4, 9, 10, 11, 12, 15, 16, 19, 20, 21]
e = a.chunk_while {|i, j| j == i + 1 }
e.each {|array| p array }
Вывод:
[1, 2] [4] [9, 10, 11, 12] [15, 16] [19, 20, 21]
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;
} Возвращает массив объектов, возвращенных блоком.
С заданным блоком вызывает блок с последовательными элементами; возвращает массив объектов, возвращенных блоком:
(0..4).map {|i| i*i } # => [0, 1, 4, 9, 16]
{foo: 0, bar: 1, baz: 2}.map {|key, value| value*2} # => [0, 2, 4]
Без заданного блока возвращает перечислитель.
Возвращает массив сглаженных объектов, возвращенных блоком.
С заданным блоком вызывает блок с последовательными элементами; возвращает сглаженный массив объектов, возвращенных блоком:
[0, 1, 2, 3].flat_map {|element| -element } # => [0, -1, -2, -3]
[0, 1, 2, 3].flat_map {|element| [element, -element] } # => [0, 0, 1, -1, 2, -2, 3, -3]
[[0, 1], [2, 3]].flat_map {|e| e + [100] } # => [0, 1, 100, 2, 3, 100]
{foo: 0, bar: 1, baz: 2}.flat_map {|key, value| [key, value] } # => [:foo, 0, :bar, 1, :baz, 2]
Без заданного блока возвращает перечислитель.
Псевдоним: collect_concat.
static VALUE
enum_compact(VALUE obj)
{
VALUE ary;
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, compact_i, ary);
return ary;
} Возвращает массив всех элементов, не являющихся nil:
a = [nil, 0, nil, 'a', false, nil, false, nil, 'a', nil, 0, nil] a.compact # => [0, "a", false, false, "a", 0]
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);
} Возвращает количество элементов, основанное на критериях аргумента или блока, если они заданы.
Без аргумента и без блока возвращает количество элементов:
[0, 1, 2].count # => 3
{foo: 0, bar: 1, baz: 2}.count # => 3
С заданным аргументом object возвращает количество элементов, которые == к object:
[0, 1, 2, 1].count(1) # => 2
С заданным блоком вызывает блок с каждым элементом и возвращает количество элементов, для которых блок возвращает истинное значение:
[0, 1, 2, 3].count {|element| element < 2} # => 2
{foo: 0, bar: 1, baz: 2}.count {|key, value| value < 2} # => 2
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;
} При вызове с положительным целым аргументом n и блоком, вызывает блок с каждым элементом, а затем делает это снова, пока не выполнит это n раз; возвращает nil:
a = []
(1..4).cycle(3) {|element| a.push(element) } # => nil
a # => [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
a = []
('a'..'d').cycle(2) {|element| a.push(element) }
a # => ["a", "b", "c", "d", "a", "b", "c", "d"]
a = []
{foo: 0, bar: 1, baz: 2}.cycle(2) {|element| a.push(element) }
a # => [[:foo, 0], [:bar, 1], [:baz, 2], [:foo, 0], [:bar, 1], [:baz, 2]]
Если счетчик равен нулю или отрицателен, блок не вызывается.
Если при вызове с блоком n равен nil, цикл продолжается бесконечно.
Если блок не указан, возвращает Enumerator.
Возвращает первый элемент, для которого блок возвращает истинное значение.
При наличии блока вызывает блок с последовательными элементами коллекции; возвращает первый элемент, для которого блок возвращает истинное значение:
(0..9).find {|element| element > 2} # => 3
Если такой элемент не найден, вызывает if_none_proc и возвращает его возвращаемое значение.
(0..9).find(proc {false}) {|element| element > 12} # => false
{foo: 0, bar: 1, baz: 2}.find {|key, value| key.start_with?('b') } # => [:bar, 1]
{foo: 0, bar: 1, baz: 2}.find(proc {[]}) {|key, value| key.start_with?('c') } # => []
Без блока возвращает перечислитель.
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, возвращает массив, содержащий все элементы, кроме первых n элементов:
r = (1..4)
r.drop(3) # => [4]
r.drop(2) # => [3, 4]
r.drop(1) # => [2, 3, 4]
r.drop(0) # => [1, 2, 3, 4]
r.drop(50) # => []
h = {foo: 0, bar: 1, baz: 2, bat: 3}
h.drop(2) # => [[:baz, 2], [:bat, 3]]
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;
} Вызывает блок с последовательными элементами до тех пор, пока блок возвращает истинное значение; возвращает массив всех элементов после этой точки:
(1..4).drop_while{|i| i < 3 } # => [3, 4]
h = {foo: 0, bar: 1, baz: 2}
a = h.drop_while{|element| key, value = *element; value < 2 }
a # => [[:baz, 2]]
Без блока возвращает Enumerator.
static VALUE
enum_each_cons(VALUE obj, VALUE n)
{
long size = NUM2LONG(n);
struct MEMO *memo;
int arity;
if (size <= 0) rb_raise(rb_eArgError, "invalid size");
RETURN_SIZED_ENUMERATOR(obj, 1, &n, enum_each_cons_size);
arity = rb_block_arity();
if (enum_size_over_p(obj, size)) return obj;
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 obj;
} Вызывает блок для каждой последовательной перекрывающейся n-кортежа элементов; возвращает self:
a = []
(1..5).each_cons(3) {|element| a.push(element) }
a # => [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
a = []
h = {foo: 0, bar: 1, baz: 2, bam: 3}
h.each_cons(2) {|element| a.push(element) }
a # => [[[:foo, 0], [:bar, 1]], [[:bar, 1], [:baz, 2]], [[:baz, 2], [:bam, 3]]]
Без блока возвращает Enumerator.
static VALUE
enum_each_entry(int argc, VALUE *argv, VALUE obj)
{
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
rb_block_call(obj, id_each, argc, argv, each_val_i, 0);
return obj;
} Вызывает заданный блок для каждого элемента, преобразуя несколько значений из yield в массив; возвращает self:
a = []
(1..4).each_entry {|element| a.push(element) } # => 1..4
a # => [1, 2, 3, 4]
a = []
h = {foo: 0, bar: 1, baz:2}
h.each_entry {|element| a.push(element) }
# => {:foo=>0, :bar=>1, :baz=>2}
a # => [[:foo, 0], [:bar, 1], [:baz, 2]]
class Foo
include Enumerable
def each
yield 1
yield 1, 2
yield
end
end
Foo.new.each_entry {|yielded| p yielded }
Вывод:
1 [1, 2] nil
Без блока возвращает Enumerator.
static VALUE
enum_each_slice(VALUE obj, VALUE n)
{
long size = NUM2LONG(n);
VALUE ary;
struct MEMO *memo;
int arity;
if (size <= 0) rb_raise(rb_eArgError, "invalid slice size");
RETURN_SIZED_ENUMERATOR(obj, 1, &n, enum_each_slice_size);
size = limit_by_enum_size(obj, size);
ary = rb_ary_new2(size);
arity = rb_block_arity();
memo = MEMO_NEW(ary, dont_recycle_block_arg(arity), size);
rb_block_call(obj, id_each, 0, 0, each_slice_i, (VALUE)memo);
ary = memo->v1;
if (RARRAY_LEN(ary) > 0) rb_yield(ary);
return obj;
} Вызывает блок для каждого последовательного непересекающегося n-кортежа элементов; возвращает self:
a = []
(1..10).each_slice(3) {|tuple| a.push(tuple) }
a # => [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
a = []
h = {foo: 0, bar: 1, baz: 2, bat: 3, bam: 4}
h.each_slice(2) {|tuple| a.push(tuple) }
a # => [[[:foo, 0], [:bar, 1]], [[:baz, 2], [:bat, 3]], [[:bam, 4]]]
Без блока возвращает Enumerator.
static VALUE
enum_each_with_index(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo;
RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);
memo = MEMO_NEW(0, 0, 0);
rb_block_call(obj, id_each, argc, argv, each_with_index_i, (VALUE)memo);
return obj;
} При наличии блока вызывает блок с каждым элементом и его индексом; возвращает self:
h = {}
(1..4).each_with_index {|element, i| h[element] = i } # => 1..4
h # => {1=>0, 2=>1, 3=>2, 4=>3}
h = {}
%w[a b c d].each_with_index {|element, i| h[element] = i }
# => ["a", "b", "c", "d"]
h # => {"a"=>0, "b"=>1, "c"=>2, "d"=>3}
a = []
h = {foo: 0, bar: 1, baz: 2}
h.each_with_index {|element, i| a.push([i, element]) }
# => {:foo=>0, :bar=>1, :baz=>2}
a # => [[0, [:foo, 0]], [1, [:bar, 1]], [2, [:baz, 2]]]
Без блока возвращает Enumerator.
static VALUE
enum_each_with_object(VALUE obj, VALUE memo)
{
RETURN_SIZED_ENUMERATOR(obj, 1, &memo, enum_size);
rb_block_call(obj, id_each, 0, 0, each_with_object_i, memo);
return memo;
} Вызывает блок один раз для каждого элемента, передавая элемент и указанный объект:
(1..4).each_with_object([]) {|i, a| a.push(i**2) } # => [1, 4, 9, 16]
h.each_with_object({}) {|element, h| k, v = *element; h[v] = k }
# => {0=>:foo, 1=>:bar, 2=>:baz}
Без блока возвращает Enumerator.
Возвращает массив, содержащий элементы в self:
(0..4).to_a # => [0, 1, 2, 3, 4]
Enumerable#entries — псевдоним для Enumerable#to_a.
Возвращает массив, содержащий элементы, выбранные блоком.
При наличии блока вызывает блок с последовательными элементами; возвращает массив тех элементов, для которых блок возвращает истинное значение:
(0..9).select {|element| element % 3 == 0 } # => [0, 3, 6, 9]
a = {foo: 0, bar: 1, baz: 2}.select {|key, value| key.start_with?('b') }
a # => {:bar=>1, :baz=>2}
Без блока возвращает Enumerator.
Связанно с: reject.
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;
} Возвращает массив, содержащий истинные значения, возвращённые блоком.
При наличии блока вызывает блок с последовательными элементами; возвращает массив, содержащий каждое истинное значение, возвращённое блоком:
(0..9).filter_map {|i| i * 2 if i.even? } # => [0, 4, 8, 12, 16]
{foo: 0, bar: 1, baz: 2}.filter_map {|key, value| key if value.even? } # => [:foo, :baz]
При отсутствии блока возвращает Enumerator.
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;
} Возвращает первый элемент, для которого блок возвращает истинное значение.
При наличии блока вызывает блок с последовательными элементами коллекции; возвращает первый элемент, для которого блок возвращает истинное значение:
(0..9).find {|element| element > 2} # => 3
Если такой элемент не найден, вызывает if_none_proc и возвращает его возвращаемое значение.
(0..9).find(proc {false}) {|element| element > 12} # => false
{foo: 0, bar: 1, baz: 2}.find {|key, value| key.start_with?('b') } # => [:bar, 1]
{foo: 0, bar: 1, baz: 2}.find(proc {[]}) {|key, value| key.start_with?('c') } # => []
Без блока возвращает Enumerator.
static VALUE
enum_find_all(VALUE obj)
{
VALUE ary;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, find_all_i, ary);
return ary;
} Возвращает массив, содержащий элементы, выбранные блоком.
При наличии блока вызывает блок с последовательными элементами; возвращает массив тех элементов, для которых блок возвращает истинное значение:
(0..9).select {|element| element % 3 == 0 } # => [0, 3, 6, 9]
a = {foo: 0, bar: 1, baz: 2}.select {|key, value| key.start_with?('b') }
a # => {:bar=>1, :baz=>2}
Без блока возвращает Enumerator.
Связанно с: reject.
static VALUE
enum_find_index(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo; /* [return value, current index, ] */
VALUE condition_value = Qnil;
rb_block_call_func *func;
if (argc == 0) {
RETURN_ENUMERATOR(obj, 0, 0);
func = find_index_iter_i;
}
else {
rb_scan_args(argc, argv, "1", &condition_value);
if (rb_block_given_p()) {
rb_warn("given block not used");
}
func = find_index_i;
}
memo = MEMO_NEW(Qnil, condition_value, 0);
rb_block_call(obj, id_each, 0, 0, func, (VALUE)memo);
return memo->v1;
} Возвращает индекс первого элемента, удовлетворяющего заданному критерию, или nil, если такой элемент не найден.
При передаче аргумента object, возвращает индекс первого элемента, который == object:
['a', 'b', 'c', 'b'].find_index('b') # => 1
При наличии блока вызывает блок с последовательными элементами; возвращает индекс первого элемента, для которого блок возвращает истинное значение:
['a', 'b', 'c', 'b'].find_index {|element| element.start_with?('b') } # => 1
{foo: 0, bar: 1, baz: 2}.find_index {|key, value| value > 1 } # => 2
Без аргумента и блока возвращает Enumerator.
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;
}
} Возвращает первый элемент или элементы.
Без аргумента возвращает первый элемент или nil, если такового нет:
(1..4).first # => 1
%w[a b c].first # => "a"
{foo: 1, bar: 1, baz: 2}.first # => [:foo, 1]
[].first # => nil
С целым аргументом n, возвращает массив, содержащий первые n существующие элементы:
(1..4).first(2) # => [1, 2]
%w[a b c d].first(3) # => ["a", "b", "c"]
%w[a b c d].first(50) # => ["a", "b", "c", "d"]
{foo: 1, bar: 1, baz: 2}.first(2) # => [[:foo, 1], [:bar, 1]]
[].first(2) # => []
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;
} Возвращает массив с уплощенными объектами, возвращёнными блоком.
При наличии блока вызывает блок с последовательными элементами; возвращает уплощённый массив объектов, возвращённых блоком:
[0, 1, 2, 3].flat_map {|element| -element } # => [0, -1, -2, -3]
[0, 1, 2, 3].flat_map {|element| [element, -element] } # => [0, 0, 1, -1, 2, -2, 3, -3]
[[0, 1], [2, 3]].flat_map {|e| e + [100] } # => [0, 1, 100, 2, 3, 100]
{foo: 0, bar: 1, baz: 2}.flat_map {|key, value| [key, value] } # => [:foo, 0, :bar, 1, :baz, 2]
Без блока возвращает Enumerator.
Псевдоним: collect_concat.
static VALUE
enum_grep(VALUE obj, VALUE pat)
{
return enum_grep0(obj, pat, Qtrue);
} Возвращает массив объектов, основанных на элементах self, которые соответствуют заданному шаблону.
Без блока возвращает массив, содержащий каждый элемент, для которого pattern === element является true:
a = ['foo', 'bar', 'car', 'moo'] a.grep(/ar/) # => ["bar", "car"] (1..10).grep(3..8) # => [3, 4, 5, 6, 7, 8] ['a', 'b', 0, 1].grep(Integer) # => [0, 1]
При наличии блока вызывает блок с каждым соответствующим элементом и возвращает массив, содержащий каждый объект, возвращённый блоком:
a = ['foo', 'bar', 'car', 'moo']
a.grep(/ar/) {|element| element.upcase } # => ["BAR", "CAR"]
Связанно с: grep_v.
static VALUE
enum_grep_v(VALUE obj, VALUE pat)
{
return enum_grep0(obj, pat, Qfalse);
} Возвращает массив объектов, основанный на элементах self, которые не соответствуют заданному шаблону.
Без блока возвращает массив, содержащий каждый элемент, для которого pattern === element не false:
a = ['foo', 'bar', 'car', 'moo'] a.grep_v(/ar/) # => ["foo", "moo"] (1..10).grep_v(3..8) # => [1, 2, 9, 10] ['a', 'b', 0, 1].grep_v(Integer) # => ["a", "b"]
С блоком вызывает блок с каждым элементом, не соответствующим шаблону, и возвращает массив, содержащий каждый объект, возвращённый блоком:
a = ['foo', 'bar', 'car', 'moo']
a.grep_v(/ar/) {|element| element.upcase } # => ["FOO", "MOO"]
Связанно с: grep.
static VALUE
enum_group_by(VALUE obj)
{
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
return enum_hashify(obj, 0, 0, group_by_i);
} С блоком возвращает словарь:
-
Каждый ключ — это возвращаемое значение из блока.
-
Каждое значение — это массив тех элементов, для которых блок вернул этот ключ.
Примеры:
g = (1..6).group_by {|i| i%3 }
g # => {1=>[1, 4], 2=>[2, 5], 0=>[3, 6]}
h = {foo: 0, bar: 1, baz: 0, bat: 1}
g = h.group_by {|key, value| value }
g # => {0=>[[:foo, 0], [:baz, 0]], 1=>[[:bar, 1], [:bat, 1]]}
Без блока возвращает Enumerator.
Возвращает, является ли для любого элемента object == element:
(1..4).include?(2) # => true
(1..4).include?(5) # => false
(1..4).include?('2') # => false
%w[a b c d].include?('b') # => true
%w[a b c d].include?('2') # => false
{foo: 0, bar: 1, baz: 2}.include?(:foo) # => true
{foo: 0, bar: 1, baz: 2}.include?('foo') # => false
{foo: 0, bar: 1, baz: 2}.include?(0) # => false
Enumerable#member? — псевдоним для Enumerable#include?.
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;
} Возвращает объект, образованный из операндов с помощью:
-
Метода, имя которого указано в
symbol. -
Блока, которому передается каждый операнд.
С аргументом метода symbol, объединяет операнды, используя метод:
# Sum, without initial_operand. (1..4).inject(:+) # => 10 # Sum, with initial_operand. (1..4).inject(10, :+) # => 20
С блоком передает каждый операнд в блок:
# Sum of squares, without initial_operand.
(1..4).inject {|sum, n| sum + n*n } # => 30
# Sum of squares, with initial_operand.
(1..4).inject(2) {|sum, n| sum + n*n } # => 32
Операнды
Если аргумент initial_operand не указан, операндами для inject являются просто элементы self. Примеры вызовов и их операнды:
-
-
(1..4).inject(:+) -
[1, 2, 3, 4].
-
-
-
(1...4).inject(:+) -
[1, 2, 3].
-
-
-
('a'..'d').inject(:+) -
['a', 'b', 'c', 'd'].
-
-
-
('a'...'d').inject(:+) -
['a', 'b', 'c'].
-
Примеры с первым операндом (который является self.first) различных типов:
# Integer.
(1..4).inject(:+) # => 10
# Float.
[1.0, 2, 3, 4].inject(:+) # => 10.0
# Character.
('a'..'d').inject(:+) # => "abcd"
# Complex.
[Complex(1, 2), 3, 4].inject(:+) # => (8+2i)
Если аргумент initial_operand указан, операндами для inject являются это значение плюс элементы self. Примеры вызовов и их операнды:
-
-
(1..4).inject(10, :+) -
[10, 1, 2, 3, 4].
-
-
-
(1...4).inject(10, :+) -
[10, 1, 2, 3].
-
-
-
('a'..'d').inject('e', :+) -
['e', 'a', 'b', 'c', 'd'].
-
-
-
('a'...'d').inject('e', :+) -
['e', 'a', 'b', 'c'].
-
Примеры с initial_operand различных типов:
# Integer.
(1..4).inject(2, :+) # => 12
# Float.
(1..4).inject(2.0, :+) # => 12.0
# String.
('a'..'d').inject('foo', :+) # => "fooabcd"
# Array.
%w[a b c].inject(['x'], :push) # => ["x", "a", "b", "c"]
# Complex.
(1..4).inject(Complex(2, 2), :+) # => (12+2i)
Объединение заданным методом
Если аргумент имени метода symbol задан, операнды объединяются этим методом:
-
Первый и второй операнды объединяются.
-
Результат объединяется с третьим операндом.
-
Результат объединяется с четвёртым операндом.
-
И так далее.
Возвращаемое значение от inject — результат последнего объединения.
Этот вызов inject вычисляет сумму операндов:
(1..4).inject(:+) # => 10
Примеры с различными методами:
# Integer addition.
(1..4).inject(:+) # => 10
# Integer multiplication.
(1..4).inject(:*) # => 24
# Character range concatenation.
('a'..'d').inject('', :+) # => "abcd"
# String array concatenation.
%w[foo bar baz].inject('', :+) # => "foobarbaz"
# Hash update.
h = [{foo: 0, bar: 1}, {baz: 2}, {bat: 3}].inject(:update)
h # => {:foo=>0, :bar=>1, :baz=>2, :bat=>3}
# Hash conversion to nested arrays.
h = {foo: 0, bar: 1}.inject([], :push)
h # => [[:foo, 0], [:bar, 1]]
Объединение заданным блоком
Если задан блок, операнды передаются в блок:
-
Первый вызов передает первый и второй операнды.
-
Второй вызов передает результат первого вызова вместе с третьим операндом.
-
Третий вызов передает результат второго вызова вместе с четвёртым операндом.
-
И так далее.
Возвращаемое значение от inject — это возвращаемое значение последнего вызова блока.
Этот вызов inject даёт блок, который выводит мемо и элемент, а также суммирует элементы:
(1..4).inject do |memo, element|
p "Memo: #{memo}; element: #{element}"
memo + element
end # => 10
Вывод:
"Memo: 1; element: 2" "Memo: 3; element: 3" "Memo: 6; element: 4"
Enumerable#reduce — псевдоним для Enumerable#inject.
static VALUE
enumerable_lazy(VALUE obj)
{
VALUE result = lazy_to_enum_i(obj, sym_each, 0, 0, lazyenum_size, rb_keyword_given_p());
/* Qfalse indicates that the Enumerator::Lazy has no method name */
rb_ivar_set(result, id_method, Qfalse);
return result;
} Возвращает Enumerator::Lazy, который переопределяет большинство методов Enumerable для отложенного перечисления и перечисления значений только по мере необходимости.
Пример
Следующая программа находит пифагорейские тройки:
def pythagorean_triples
(1..Float::INFINITY).lazy.flat_map {|z|
(1..z).flat_map {|x|
(x..z).select {|y|
x**2 + y**2 == z**2
}.map {|y|
[x, y, z]
}
}
}
end
# show first ten pythagorean triples
p pythagorean_triples.take(10).force # take is lazy, so force is needed
p pythagorean_triples.first(10) # first is eager
# show pythagorean triples less than 100
p pythagorean_triples.take_while { |*, z| z < 100 }.force
Возвращает массив объектов, возвращённых блоком.
С блоком вызывает блок с последовательными элементами; возвращает массив объектов, возвращённых блоком:
(0..4).map {|i| i*i } # => [0, 1, 4, 9, 16]
{foo: 0, bar: 1, baz: 2}.map {|key, value| value*2} # => [0, 2, 4]
Без блока возвращает перечислитель.
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;
} Возвращает элемент с максимальным значением по заданному критерию. Порядок равных элементов неопределён и может быть неустойчивым.
Без аргумента и без блока возвращает максимальный элемент, используя собственный метод <=> для сравнения:
(1..4).max # => 4
(-4..-1).max # => -1
%w[d c b a].max # => "d"
{foo: 0, bar: 1, baz: 2}.max # => [:foo, 0]
[].max # => nil
С положительным целым аргументом n и без блока возвращает массив, содержащий первые n максимальных элементов, которые существуют:
(1..4).max(2) # => [4, 3]
(-4..-1).max(2) # => [-1, -2]
%w[d c b a].max(2) # => ["d", "c"]
{foo: 0, bar: 1, baz: 2}.max(2) # => [[:foo, 0], [:baz, 2]]
[].max(2) # => []
С блоком блок определяет максимальные элементы. Блок вызывается с двумя элементами a и b, и должен возвращать:
-
Отрицательное целое число, если
a < b. -
Ноль, если
a == b. -
Положительное целое число, если
a > b.
С блоком и без аргумента возвращает максимальный элемент, как определено блоком:
%w[xxx x xxxx xx].max {|a, b| a.size <=> b.size } # => "xxxx"
h = {foo: 0, bar: 1, baz: 2}
h.max {|pair1, pair2| pair1[1] <=> pair2[1] } # => [:baz, 2]
[].max {|a, b| a <=> b } # => nil
С блоком и положительным целым аргументом n возвращает массив, содержащий первые n максимальных элементов, как определено блоком.
%w[xxx x xxxx xx].max(2) {|a, b| a.size <=> b.size } # => ["xxxx", "xxx"]
h = {foo: 0, bar: 1, baz: 2}
h.max(2) {|pair1, pair2| pair1[1] <=> pair2[1] }
# => [[:baz, 2], [:bar, 1]]
[].max(2) {|a, b| a <=> b } # => []
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;
} Возвращает элементы, для которых блок возвращает максимальные значения.
С блоком и без аргумента возвращает элемент, для которого блок возвращает максимальное значение:
(1..4).max_by {|element| -element } # => 1
%w[a b c d].max_by {|element| -element.ord } # => "a"
{foo: 0, bar: 1, baz: 2}.max_by {|key, value| -value } # => [:foo, 0]
[].max_by {|element| -element } # => nil
С блоком и положительным целым аргументом n возвращает массив, содержащий n элементов, для которых блок возвращает максимальные значения:
(1..4).max_by(2) {|element| -element }
# => [1, 2]
%w[a b c d].max_by(2) {|element| -element.ord }
# => ["a", "b"]
{foo: 0, bar: 1, baz: 2}.max_by(2) {|key, value| -value }
# => [[:foo, 0], [:bar, 1]]
[].max_by(2) {|element| -element }
# => []
Возвращает Enumerator, если блок не задан.
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, если для любого элемента object == element, в противном случае false:
(1..4).include?(2) # => true
(1..4).include?(5) # => false
(1..4).include?('2') # => false
%w[a b c d].include?('b') # => true
%w[a b c d].include?('2') # => false
{foo: 0, bar: 1, baz: 2}.include?(:foo) # => true
{foo: 0, bar: 1, baz: 2}.include?('foo') # => false
{foo: 0, bar: 1, baz: 2}.include?(0) # => false
Enumerable#member? является псевдонимом для Enumerable#include?.
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;
} Возвращает элемент с минимальным значением согласно заданному критерию. Порядок элементов с одинаковым значением не определен и может быть неустойчивым.
Без аргумента и без блока возвращает минимальный элемент, используя собственный метод элементов <=> для сравнения:
(1..4).min # => 1
(-4..-1).min # => -4
%w[d c b a].min # => "a"
{foo: 0, bar: 1, baz: 2}.min # => [:bar, 1]
[].min # => nil
С положительным целочисленным аргументом n, и без блока, возвращает массив, содержащий первые n минимальных элементов, которые существуют:
(1..4).min(2) # => [1, 2]
(-4..-1).min(2) # => [-4, -3]
%w[d c b a].min(2) # => ["a", "b"]
{foo: 0, bar: 1, baz: 2}.min(2) # => [[:bar, 1], [:baz, 2]]
[].min(2) # => []
С блоком, блок определяет минимальные элементы. Блок вызывается с двумя элементами a и b, и должен возвращать:
-
Отрицательное целое число, если
a < b. -
Ноль, если
a == b. -
Положительное целое число, если
a > b.
С блоком и без аргумента возвращает минимальный элемент, определяемый блоком:
%w[xxx x xxxx xx].min {|a, b| a.size <=> b.size } # => "x"
h = {foo: 0, bar: 1, baz: 2}
h.min {|pair1, pair2| pair1[1] <=> pair2[1] } # => [:foo, 0]
[].min {|a, b| a <=> b } # => nil
С блоком и положительным целочисленным аргументом n возвращает массив, содержащий первые n минимальных элементов, определяемых блоком.
%w[xxx x xxxx xx].min(2) {|a, b| a.size <=> b.size } # => ["x", "xx"]
h = {foo: 0, bar: 1, baz: 2}
h.min(2) {|pair1, pair2| pair1[1] <=> pair2[1] }
# => [[:foo, 0], [:bar, 1]]
[].min(2) {|a, b| a <=> b } # => []
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;
} Возвращает элементы, для которых блок возвращает минимальные значения.
С блоком и без аргумента возвращает элемент, для которого блок возвращает минимальное значение:
(1..4).min_by {|element| -element } # => 4
%w[a b c d].min_by {|element| -element.ord } # => "d"
{foo: 0, bar: 1, baz: 2}.min_by {|key, value| -value } # => [:baz, 2]
[].min_by {|element| -element } # => nil
С блоком и положительным целочисленным аргументом n возвращает массив, содержащий n элементы, для которых блок возвращает минимальные значения:
(1..4).min_by(2) {|element| -element }
# => [4, 3]
%w[a b c d].min_by(2) {|element| -element.ord }
# => ["d", "c"]
{foo: 0, bar: 1, baz: 2}.min_by(2) {|key, value| -value }
# => [[:baz, 2], [:bar, 1]]
[].min_by(2) {|element| -element }
# => []
Возвращает Enumerator, если блок не задан.
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);
} Возвращает массив из 2 элементов, содержащий минимальный и максимальный элементы согласно заданному критерию. Порядок элементов с одинаковым значением не определен и может быть неустойчивым.
Без аргумента и без блока возвращает минимальный и максимальный элементы, используя собственный метод элементов <=> для сравнения:
(1..4).minmax # => [1, 4]
(-4..-1).minmax # => [-4, -1]
%w[d c b a].minmax # => ["a", "d"]
{foo: 0, bar: 1, baz: 2}.minmax # => [[:bar, 1], [:foo, 0]]
[].minmax # => [nil, nil]
С блоком возвращает минимальный и максимальный элементы, определяемые блоком:
%w[xxx x xxxx xx].minmax {|a, b| a.size <=> b.size } # => ["x", "xxxx"]
h = {foo: 0, bar: 1, baz: 2}
h.minmax {|pair1, pair2| pair1[1] <=> pair2[1] }
# => [[:foo, 0], [:baz, 2]]
[].minmax {|a, b| a <=> b } # => [nil, nil]
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);
} Возвращает массив из 2 элементов, содержащий элементы, для которых блок возвращает минимальное и максимальное значения:
(1..4).minmax_by {|element| -element }
# => [4, 1]
%w[a b c d].minmax_by {|element| -element.ord }
# => ["d", "a"]
{foo: 0, bar: 1, baz: 2}.minmax_by {|key, value| -value }
# => [[:baz, 2], [:foo, 0]]
[].minmax_by {|element| -element }
# => [nil, nil]
Возвращает Enumerator, если блок не задан.
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, если ни один элемент не является истинным:
(1..4).none? # => false
[nil, false].none? # => true
{foo: 0}.none? # => false
{foo: 0, bar: 1}.none? # => false
[].none? # => true
С аргументом pattern и без блока, возвращает true, если ни для одного элемента element, pattern === element:
[nil, false, 1.1].none?(Integer) # => true
%w[bar baz bat bam].none?(/m/) # => false
%w[bar baz bat bam].none?(/foo/) # => true
%w[bar baz bat bam].none?('ba') # => true
{foo: 0, bar: 1, baz: 2}.none?(Hash) # => true
{foo: 0}.none?(Array) # => false
[].none?(Integer) # => true
С блоком, возвращает true, если блок не возвращает истинное значение ни для одного элемента:
(1..4).none? {|element| element < 1 } # => true
(1..4).none? {|element| element < 2 } # => false
{foo: 0, bar: 1, baz: 2}.none? {|key, value| value < 0 } # => true
{foo: 0, bar: 1, baz: 2}.none? {|key, value| value < 1 } # => 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, если ровно один элемент является истинным:
(1..1).one? # => true
[1, nil, false].one? # => true
(1..4).one? # => false
{foo: 0}.one? # => true
{foo: 0, bar: 1}.one? # => false
[].one? # => false
С аргументом pattern и без блока, возвращает true, если для ровно одного элемента element, pattern === element:
[nil, false, 0].one?(Integer) # => true
[nil, false, 0].one?(Numeric) # => true
[nil, false, 0].one?(Float) # => false
%w[bar baz bat bam].one?(/m/) # => true
%w[bar baz bat bam].one?(/foo/) # => false
%w[bar baz bat bam].one?('ba') # => false
{foo: 0, bar: 1, baz: 2}.one?(Array) # => false
{foo: 0}.one?(Array) # => true
[].one?(Integer) # => false
С блоком, возвращает true, если блок возвращает истинное значение ровно для одного элемента:
(1..4).one? {|element| element < 2 } # => true
(1..4).one? {|element| element < 1 } # => false
{foo: 0, bar: 1, baz: 2}.one? {|key, value| value < 1 } # => true
{foo: 0, bar: 1, baz: 2}.one? {|key, value| value < 2 } # => 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);
} С блоком возвращает массив из двух массивов:
-
Первый содержит элементы, для которых блок возвращает истинное значение.
-
Второй содержит все остальные элементы.
Примеры:
p = (1..4).partition {|i| i.even? }
p # => [[2, 4], [1, 3]]
p = ('a'..'d').partition {|c| c < 'c' }
p # => [["a", "b"], ["c", "d"]]
h = {foo: 0, bar: 1, baz: 2, bat: 3}
p = h.partition {|key, value| key.start_with?('b') }
p # => [[[:bar, 1], [:baz, 2], [:bat, 3]], [[:foo, 0]]]
p = h.partition {|key, value| value < 2 }
p # => [[[:foo, 0], [:bar, 1]], [[:baz, 2], [:bat, 3]]]
Без блока возвращает Enumerator.
Связанные: Enumerable#group_by.
Возвращает объект, сформированный из операндов с помощью:
-
Метода, имя которого
symbol. -
Блока, в который передаётся каждый операнд.
С аргументом имени метода symbol, комбинирует операнды, используя метод:
# Sum, without initial_operand. (1..4).inject(:+) # => 10 # Sum, with initial_operand. (1..4).inject(10, :+) # => 20
С блоком, передаёт каждый операнд в блок:
# Sum of squares, without initial_operand.
(1..4).inject {|sum, n| sum + n*n } # => 30
# Sum of squares, with initial_operand.
(1..4).inject(2) {|sum, n| sum + n*n } # => 32
Операнды
Если аргумент initial_operand не указан, операнды для inject — это просто элементы self. Примеры вызовов и их операнды:
-
-
(1..4).inject(:+) -
[1, 2, 3, 4].
-
-
-
(1...4).inject(:+) -
[1, 2, 3].
-
-
-
('a'..'d').inject(:+) -
['a', 'b', 'c', 'd'].
-
-
-
('a'...'d').inject(:+) -
['a', 'b', 'c'].
-
Примеры с первым операндом (который self.first) различных типов:
# Integer.
(1..4).inject(:+) # => 10
# Float.
[1.0, 2, 3, 4].inject(:+) # => 10.0
# Character.
('a'..'d').inject(:+) # => "abcd"
# Complex.
[Complex(1, 2), 3, 4].inject(:+) # => (8+2i)
Если аргумент initial_operand указан, операнды для inject — это это значение плюс элементы self. Примеры вызовов и их операнды:
-
-
(1..4).inject(10, :+) -
[10, 1, 2, 3, 4].
-
-
-
(1...4).inject(10, :+) -
[10, 1, 2, 3].
-
-
-
('a'..'d').inject('e', :+) -
['e', 'a', 'b', 'c', 'd'].
-
-
-
('a'...'d').inject('e', :+) -
['e', 'a', 'b', 'c'].
-
Примеры с initial_operand различных типов:
# Integer.
(1..4).inject(2, :+) # => 12
# Float.
(1..4).inject(2.0, :+) # => 12.0
# String.
('a'..'d').inject('foo', :+) # => "fooabcd"
# Array.
%w[a b c].inject(['x'], :push) # => ["x", "a", "b", "c"]
# Complex.
(1..4).inject(Complex(2, 2), :+) # => (12+2i)
Комбинирование с заданным методом
Если аргумент имени метода symbol указан, операнды комбинируются с помощью этого метода:
-
Первый и второй операнды комбинируются.
-
Результат комбинируется с третьим операндом.
-
Результат комбинируется с четвёртым операндом.
-
И так далее.
Возвращаемое значение от inject — результат последнего комбинирования.
Этот вызов inject вычисляет сумму операндов:
(1..4).inject(:+) # => 10
Примеры с различными методами:
# Integer addition.
(1..4).inject(:+) # => 10
# Integer multiplication.
(1..4).inject(:*) # => 24
# Character range concatenation.
('a'..'d').inject('', :+) # => "abcd"
# String array concatenation.
%w[foo bar baz].inject('', :+) # => "foobarbaz"
# Hash update.
h = [{foo: 0, bar: 1}, {baz: 2}, {bat: 3}].inject(:update)
h # => {:foo=>0, :bar=>1, :baz=>2, :bat=>3}
# Hash conversion to nested arrays.
h = {foo: 0, bar: 1}.inject([], :push)
h # => [[:foo, 0], [:bar, 1]]
Комбинирование с заданным блоком
Если указан блок, операнды передаются в блок:
-
Первый вызов передаёт первый и второй операнды.
-
Второй вызов передаёт результат первого вызова вместе с третьим операндом.
-
Третий вызов передаёт результат второго вызова вместе с четвёртым операндом.
-
И так далее.
Возвращаемое значение от inject — возвращаемое значение последнего вызова блока.
Этот вызов inject передаёт блок, который записывает данные и суммирует элементы:
(1..4).inject do |memo, element|
p "Memo: #{memo}; element: #{element}"
memo + element
end # => 10
Вывод:
"Memo: 1; element: 2" "Memo: 3; element: 3" "Memo: 6; element: 4"
Enumerable#reduce — псевдоним для Enumerable#inject.
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;
} Возвращает массив объектов, отклоненных блоком.
При указанном блоке вызывает блок с последующими элементами; возвращает массив тех элементов, для которых блок возвращает nil или false.
(0..9).reject {|i| i * 2 if i.even? } # => [1, 3, 5, 7, 9]
{foo: 0, bar: 1, baz: 2}.reject {|key, value| key if value.odd? } # => {:foo=>0, :baz=>2}
Без блока возвращает Enumerator.
Связанно с: select.
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;
} При указанном блоке вызывает блок с каждым элементом, но в обратном порядке; возвращает self:
a = []
(1..4).reverse_each {|element| a.push(-element) } # => 1..4
a # => [-4, -3, -2, -1]
a = []
%w[a b c d].reverse_each {|element| a.push(element) }
# => ["a", "b", "c", "d"]
a # => ["d", "c", "b", "a"]
a = []
h.reverse_each {|element| a.push(element) }
# => {:foo=>0, :bar=>1, :baz=>2}
a # => [[:baz, 2], [:bar, 1], [:foo, 0]]
Без блока возвращает Enumerator.
Возвращает массив, содержащий выбранные блоком элементы.
При указанном блоке вызывает блок с последующими элементами; возвращает массив тех элементов, для которых блок возвращает истинное значение:
(0..9).select {|element| element % 3 == 0 } # => [0, 3, 6, 9]
a = {foo: 0, bar: 1, baz: 2}.select {|key, value| key.start_with?('b') }
a # => {:bar=>1, :baz=>2}
Без блока возвращает Enumerator.
Связанно с: reject.
static VALUE
enum_slice_after(int argc, VALUE *argv, VALUE enumerable)
{
VALUE enumerator;
VALUE pat = Qnil, pred = Qnil;
if (rb_block_given_p()) {
if (0 < argc)
rb_raise(rb_eArgError, "both pattern and block are given");
pred = rb_block_proc();
}
else {
rb_scan_args(argc, argv, "1", &pat);
}
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, id_sliceafter_enum, enumerable);
rb_ivar_set(enumerator, id_sliceafter_pat, pat);
rb_ivar_set(enumerator, id_sliceafter_pred, pred);
rb_block_call(enumerator, idInitialize, 0, 0, sliceafter_i, enumerator);
return enumerator;
} С аргументом pattern, возвращает перечислитель, который использует шаблон для разбиения элементов на массивы («куски»). Элемент завершает текущий кусок, если element === pattern:
a = %w[foo bar fop for baz fob fog bam foy]
e = a.slice_after(/ba/) # => #<Enumerator: ...>
e.each {|array| p array }
Вывод:
["foo", "bar"] ["fop", "for", "baz"] ["fob", "fog", "bam"] ["foy"]
С блоком возвращает перечислитель, который использует блок для разбиения элементов на массивы. Элемент завершает текущий кусок, если его возвращаемое значение блока истинно:
e = (1..20).slice_after {|i| i % 4 == 2 } # => #<Enumerator: ...>
e.each {|array| p array }
Вывод:
[1, 2] [3, 4, 5, 6] [7, 8, 9, 10] [11, 12, 13, 14] [15, 16, 17, 18] [19, 20]
Другие методы класса 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, id_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, id_slicebefore_sep_pat, sep_pat);
}
rb_ivar_set(enumerator, id_slicebefore_enumerable, enumerable);
rb_block_call(enumerator, idInitialize, 0, 0, slicebefore_i, enumerator);
return enumerator;
} С аргументом pattern, возвращает перечислитель, который использует шаблон для разбиения элементов на массивы («куски»). Элемент начинает новый кусок, если element === pattern (или если это первый элемент).
a = %w[foo bar fop for baz fob fog bam foy]
e = a.slice_before(/ba/) # => #<Enumerator: ...>
e.each {|array| p array }
Вывод:
["foo"] ["bar", "fop", "for"] ["baz", "fob", "fog"] ["bam", "foy"]
С блоком возвращает перечислитель, который использует блок для разбиения элементов на массивы. Элемент начинает новый кусок, если его возвращаемое значение блока истинно (или если это первый элемент):
e = (1..20).slice_before {|i| i % 4 == 2 } # => #<Enumerator: ...>
e.each {|array| p array }
Вывод:
[1] [2, 3, 4, 5] [6, 7, 8, 9] [10, 11, 12, 13] [14, 15, 16, 17] [18, 19, 20]
Другие методы класса 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. Таким образом, каждое сообщение можно извлечь, используя разбиение по строке 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, id_slicewhen_enum, enumerable);
rb_ivar_set(enumerator, id_slicewhen_pred, pred);
rb_ivar_set(enumerator, id_slicewhen_inverted, Qfalse);
rb_block_call(enumerator, idInitialize, 0, 0, slicewhen_i, enumerator);
return enumerator;
} Возвращаемый перечислитель использует блок для разбиения элементов на массивы («куски»); он вызывает блок с каждым элементом и его преемником; начинает новый кусок только в том случае, если блок возвращает истинное значение:
a = [0, 1, 2, 4, 5, 6, 8, 9]
e = a.slice_when {|i, j| j != i + 1 }
e.each {|array| p array }
Вывод:
[0, 1, 2] [4, 5, 6] [8, 9]
static VALUE
enum_sort(VALUE obj)
{
return rb_ary_sort_bang(enum_to_a(0, 0, obj));
} Возвращает массив, содержащий отсортированные элементы self.
Без блока сортировка сравнивает элементы с помощью метода <=>:
%w[b c a d].sort # => ["a", "b", "c", "d"]
{foo: 0, bar: 1, baz: 2}.sort # => [[:bar, 1], [:baz, 2], [:foo, 0]]
С указанным блоком, сравнения в блоке определяют порядок. Блок вызывается с двумя элементами a и b, и должен возвращать:
-
Отрицательное целое число, если
a < b. -
Ноль, если
a == b. -
Положительное целое число, если
a > b.
Примеры:
a = %w[b c a d]
a.sort {|a, b| b <=> a } # => ["d", "c", "b", "a"]
h = {foo: 0, bar: 1, baz: 2}
h.sort {|a, b| b <=> a } # => [[:foo, 0], [:baz, 2], [:bar, 1]]
См. также sort_by. Он реализует преобразование Шварца, которое полезно, когда вычисление ключа или сравнение является дорогостоящим.
static VALUE
enum_sort_by(VALUE obj)
{
VALUE ary, buf;
struct MEMO *memo;
long i;
struct sort_by_data *data;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
if (RB_TYPE_P(obj, T_ARRAY) && RARRAY_LEN(obj) <= LONG_MAX/2) {
ary = rb_ary_new2(RARRAY_LEN(obj)*2);
}
else {
ary = rb_ary_new();
}
RBASIC_CLEAR_CLASS(ary);
buf = rb_ary_tmp_new(SORT_BY_BUFSIZE*2);
rb_ary_store(buf, SORT_BY_BUFSIZE*2-1, Qnil);
memo = MEMO_NEW(0, 0, 0);
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;
} При заданном блоке возвращает массив элементов self, отсортированный по значению, возвращаемому блоком для каждого элемента. Порядок равных элементов не определен и может быть неустойчивым.
Примеры:
a = %w[xx xxx x xxxx]
a.sort_by {|s| s.size } # => ["x", "xx", "xxx", "xxxx"]
a.sort_by {|s| -s.size } # => ["xxxx", "xxx", "xx", "x"]
h = {foo: 2, bar: 1, baz: 0}
h.sort_by{|key, value| value } # => [[:baz, 0], [:bar, 1], [:foo, 2]]
h.sort_by{|key, value| key } # => [[:bar, 1], [:baz, 0], [:foo, 2]]
Без заданного блока возвращает Enumerator.
Текущая реализация 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_BIGNUM_TYPE_P(beg)) &&
(FIXNUM_P(end) || RB_BIGNUM_TYPE_P(end))) {
return int_range_sum(beg, end, excl, memo.v);
}
}
if (RB_TYPE_P(obj, T_HASH) &&
rb_method_basic_definition_p(CLASS_OF(obj), id_each))
hash_sum(obj, &memo);
else
rb_block_call(obj, id_each, 0, 0, enum_sum_i, (VALUE)&memo);
if (memo.float_value) {
return DBL2NUM(memo.f + memo.c);
}
else {
if (memo.n != 0)
memo.v = rb_fix_plus(LONG2FIX(memo.n), memo.v);
if (memo.r != Qundef) {
memo.v = rb_rational_plus(memo.r, memo.v);
}
return memo.v;
}
} Без заданного блока возвращает сумму initial_value и элементов:
(1..100).sum # => 5050
(1..100).sum(1) # => 5051
('a'..'d').sum('foo') # => "fooabcd"
В целом, сумма вычисляется с помощью методов + и each; для повышения производительности эти методы могут не использоваться, и поэтому любое переопределение этих методов здесь может не повлиять.
Один из таких оптимизаций: когда возможно, используется формула суммирования Гаусса n(n+1)/2:
100 * (100 + 1) / 2 # => 5050
При заданном блоке вызывается блок для каждого элемента; возвращает сумму initial_value и значений, возвращаемых блоком:
(1..4).sum {|i| i*i } # => 30
(1..4).sum(100) {|i| i*i } # => 130
h = {a: 0, b: 1, c: 2, d: 3, e: 4, f: 5}
h.sum {|key, value| value.odd? ? value : 0 } # => 9
('a'..'f').sum('x') {|c| c < 'd' ? c : '' } # => "xabc"
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, возвращает первые n элементов:
r = (1..4)
r.take(2) # => [1, 2]
r.take(0) # => []
h = {foo: 0, bar: 1, baz: 2, bat: 3}
h.take(2) # => [[:foo, 0], [:bar, 1]]
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;
} Вызывает блок для последовательных элементов, пока блок возвращает истинное значение; возвращает массив всех элементов до этого момента:
(1..4).take_while{|i| i < 3 } # => [1, 2]
h = {foo: 0, bar: 1, baz: 2}
h.take_while{|element| key, value = *element; value < 2 }
# => [[:foo, 0], [:bar, 1]]
Без заданного блока возвращает Enumerator.
static VALUE
enum_tally(int argc, VALUE *argv, VALUE obj)
{
VALUE hash;
if (rb_check_arity(argc, 0, 1)) {
hash = rb_convert_type(argv[0], T_HASH, "Hash", "to_hash");
rb_check_frozen(hash);
}
else {
hash = rb_hash_new();
}
return enum_hashify_into(obj, 0, 0, tally_i, hash);
} Возвращает хеш, содержащий счётчики равных элементов:
-
Каждый ключ — элемент
self. -
Каждое значение — количество элементов, равных этому ключу.
Без аргументов:
%w[a b c b c a c b].tally # => {"a"=>2, "b"=>3, "c"=>3}
С аргументом-хешем, этот хеш используется для подсчёта (вместо нового хеша) и возвращается; это может быть полезно для накопления подсчётов по нескольким перечисляемым объектам:
hash = {}
hash = %w[a c d b c a].tally(hash)
hash # => {"a"=>2, "c"=>2, "d"=>1, "b"=>1}
hash = %w[b a z].tally(hash)
hash # => {"a"=>3, "c"=>2, "d"=>1, "b"=>2, "z"=>1}
hash = %w[b a m].tally(hash)
hash # => {"a"=>4, "c"=>2, "d"=>1, "b"=>3, "z"=>1, "m"=> 1}
static VALUE
enum_to_a(int argc, VALUE *argv, VALUE obj)
{
VALUE ary = rb_ary_new();
rb_block_call_kw(obj, id_each, argc, argv, collect_all, ary, RB_PASS_CALLED_KEYWORDS);
return ary;
} Возвращает массив, содержащий элементы в self:
(0..4).to_a # => [0, 1, 2, 3, 4]
Enumerable#entries — псевдоним для Enumerable#to_a.
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);
} Когда self состоит из массивов из 2 элементов, возвращает хеш, каждая запись которого — пара ключ-значение, сформированная из одного из этих массивов:
[[:foo, 0], [:bar, 1], [:baz, 2]].to_h # => {:foo=>0, :bar=>1, :baz=>2}
Когда задан блок, блок вызывается с каждым элементом self; блок должен возвращать массив из 2 элементов, который становится парой ключ-значение в возвращаемом хеше:
(0..3).to_h {|i| [i, i ** 2]} # => {0=>0, 1=>1, 2=>4, 3=>9}
Вызывает исключение, если элемент self не является массивом из 2 элементов и блок не передан.
# File lib/set.rb, line 855 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;
} Без блока возвращает новый массив, содержащий только уникальные элементы; в массиве нет двух элементов e0 и e1 таких, что e0.eql?(e1):
%w[a b c c b a a b c].uniq # => ["a", "b", "c"] [0, 1, 2, 2, 1, 0, 0, 1, 2].uniq # => [0, 1, 2]
С блоком возвращает новый массив, содержащий только те элементы, для которых блок возвращает уникальное значение:
a = [0, 1, 2, 3, 4, 5, 5, 4, 3, 2, 1]
a.uniq {|i| i.even? ? i : 0 } # => [0, 2, 4]
a = %w[a b c d e e d c b a a b c d e]
a.uniq {|c| c < 'c' } # => ["a", "c"]
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;
} Без блока возвращает новый массив new_array размера self.size, элементы которого — массивы. Каждый вложенный массив new_array[n] имеет размер other_enums.size+1, и содержит:
-
n-й элемент self. -
n-й элемент каждого изother_enums.
Если все перечисляемые объекты other_enums и self имеют одинаковый размер, все элементы включаются в результат, и нет заполнения до размера:
a = [:a0, :a1, :a2, :a3]
b = [:b0, :b1, :b2, :b3]
c = [:c0, :c1, :c2, :c3]
d = a.zip(b, c)
d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, :c2], [:a3, :b3, :c3]]
f = {foo: 0, bar: 1, baz: 2}
g = {goo: 3, gar: 4, gaz: 5}
h = {hoo: 6, har: 7, haz: 8}
d = f.zip(g, h)
d # => [
# [[:foo, 0], [:goo, 3], [:hoo, 6]],
# [[:bar, 1], [:gar, 4], [:har, 7]],
# [[:baz, 2], [:gaz, 5], [:haz, 8]]
# ]
Если какой-либо перечисляемый объект в other_enums меньше, чем self, заполняет до self.size значением nil:
a = [:a0, :a1, :a2, :a3] b = [:b0, :b1, :b2] c = [:c0, :c1] d = a.zip(b, c) d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, nil], [:a3, nil, nil]]
Если какой-либо перечисляемый объект в other_enums больше, чем self, его хвостовые элементы игнорируются:
a = [:a0, :a1, :a2, :a3] b = [:b0, :b1, :b2, :b3, :b4] c = [:c0, :c1, :c2, :c3, :c4, :c5] d = a.zip(b, c) d # => [[:a0, :b0, :c0], [:a1, :b1, :c1], [:a2, :b2, :c2], [:a3, :b3, :c3]]
Когда задан блок, вызывается блок для каждого из подмассивов (сформированных как указано выше); возвращает null:
a = [:a0, :a1, :a2, :a3]
b = [:b0, :b1, :b2, :b3]
c = [:c0, :c1, :c2, :c3]
a.zip(b, c) {|sub_array| p sub_array} # => nil
Вывод:
[:a0, :b0, :c0] [:a1, :b1, :c1] [:a2, :b2, :c2] [:a3, :b3, :c3]
Ruby Core © 1993–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.