модуль 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, не изменяя его:
Начальные, конечные или все элементы:
-
first: Возвращает первый элемент или начальные элементы. -
take: Возвращает указанное количество начальных элементов. -
drop: Возвращает указанное количество конечных элементов. -
take_while: Возвращает начальные элементы, как указано в блоке. -
drop_while: Возвращает конечные элементы, как указано в блоке.
Минимальные и максимальные значения элементов:
-
min: Возвращает элементы с наименьшими значениями среди элементов, определённые по<=>или заданному блоку. -
max: Возвращает элементы с наибольшими значениями среди элементов, определённые по<=>или заданному блоку. -
minmax: Возвращает массив из 2 элементовArrayсодержащий наименьший и наибольший элементы. -
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_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: Вызывает блок с каждым последующим элементом в обратном порядке.
Другие методы
-
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:
Эти классы стандартной библиотеки Ruby включают Enumerable:
Практически все методы в Enumerable вызывают метод #each в включающем классе:
-
Hash#eachвозвращает следующую пару ключ-значение в виде 2-элементногоArray. -
Struct#eachвозвращает следующую пару имя-значение в виде 2-элементногоArray. -
Для других классов выше,
#eachвозвращает следующий объект из коллекции.
О примерах
Приведенные примеры кода для методов Enumerable:
-
Всегда демонстрируют использование одного или нескольких классов, подобных массивам (часто
Arrayсам по себе). -
Иногда показывают использование класса, подобного хэшу. Однако для некоторых методов такое использование не имеет смысла, поэтому оно не показано. Например,
tallyнайдет ровно один экземпляр каждого элементаHash.
Общедоступные методы экземпляра
static VALUE
enum_all(int argc, VALUE *argv, VALUE obj)
{
struct MEMO *memo = MEMO_ENUM_NEW(Qtrue);
WARN_UNUSED_BLOCK(argc);
rb_block_call(obj, id_each, 0, 0, ENUMFUNC(all), (VALUE)memo);
return memo->v1;
} Возвращает значение true, если каждый элемент удовлетворяет заданному критерию.
Если self не содержит элементов, возвращает true, и аргумент или блок не используются.
Без аргумента и без блока возвращает true, если каждый элемент является истинным значением:
(1..4).all? # => true %w[a b c d].all? # => true [1, 2, nil].all? # => false ['a','b', false].all? # => false [].all? # => true
С аргументом pattern и без блока возвращает true, если для каждого элемента 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
С заданным блоком возвращает 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;
} Возвращает true, если хотя бы один элемент удовлетворяет заданному критерию.
Если self не содержит элементов, возвращает false, и аргумент или блок не используются.
Без аргумента и без блока возвращает true, если хотя бы один элемент является истинным значением:
(1..4).any? # => true %w[a b c d].any? # => true [1, false, nil].any? # => true [].any? # => false
С аргументом pattern и без блока возвращает true, если для любого элемента 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
С заданным блоком возвращает true, если блок возвращает истинное значение для любого элемента:
(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 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
}
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;
} Создаёт перечислитель для каждого элемента в кусках. Началом кусков определяются в блоке.
Этот метод разбивает каждый кусок, используя смежные элементы, elt_before и elt_after, в перечислителе-получателе. Этот метод разбивает куски между elt_before и elt_after, где блок возвращает false.
Блок вызывается с длиной перечислителя-получателя минус один.
Результат перечислителя возвращает элементы кусков как массив. Так что each метод может быть вызван следующим образом:
enum.chunk_while { |elt_before, elt_after| bool }.each { |ary| ... } Другие методы класса Enumerator и модуля Enumerable, такие как to_a, map, и т. д., также могут быть использованы.
Например, возрастающую последовательность можно разбить на куски следующим образом:
a = [1,2,4,9,10,11,12,15,16,19,20,21]
b = a.chunk_while {|i, j| i+1 == j }
p b.to_a #=> [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
p c #=> [[1, 2], [4], "9-12", [15, 16], "19-21"]
d = c.join(",")
p d #=> "1,2,4,9-12,15,16,19-21"
Возрастающую (неубывающую) последовательность можно разбить на куски следующим образом:
a = [0, 9, 2, 2, 3, 2, 7, 5, 9, 5]
p a.chunk_while {|i, j| i <= j }.to_a
#=> [[0, 9], [2, 2, 3], [2, 7], [5, 9], [5]]
Смежные чётные и нечётные числа можно разбить на куски следующим образом: (Enumerable#chunk — это ещё один способ сделать это.)
a = [7, 5, 9, 2, 0, 7, 9, 4, 2, 0]
p a.chunk_while {|i, j| i.even? == j.even? }.to_a
#=> [[7, 5, 9], [2, 0], [7, 9], [4, 2, 0]]
Enumerable#slice_when делает то же самое, за исключением разделения, когда блок возвращает true вместо false.
static VALUE
enum_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.
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]
{foo: 0, bar: 1, baz: 2}.each_with_object({}) {|(k, v), h| h[v] = k }
# => {0=>:foo, 1=>:bar, 2=>:baz}
Без блока возвращает Enumerator.
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_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.
Возвращает значение true или false в зависимости от того, содержится ли хотя бы один элемент 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
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;
int num_args;
if (rb_block_given_p()) {
num_args = rb_scan_args(argc, argv, "02", &init, &op);
}
else {
num_args = rb_scan_args(argc, argv, "11", &init, &op);
}
switch (num_args) {
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 (UNDEF_P(memo->v1)) 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"
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]
Без заданного блока возвращает Enumerator.
static VALUE
enum_max(int argc, VALUE *argv, VALUE obj)
{
VALUE memo;
struct max_t *m = NEW_MEMO_FOR(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;
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 (UNDEF_P(result)) 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_min(int argc, VALUE *argv, VALUE obj)
{
VALUE memo;
struct min_t *m = NEW_MEMO_FOR(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;
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 (UNDEF_P(result)) 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_MEMO_FOR(struct minmax_t, memo);
m->min = Qundef;
m->last = Qundef;
if (rb_block_given_p()) {
rb_block_call(obj, id_each, 0, 0, minmax_ii, memo);
if (!UNDEF_P(m->last))
minmax_ii_update(m->last, m->last, m);
}
else {
rb_block_call(obj, id_each, 0, 0, minmax_i, memo);
if (!UNDEF_P(m->last))
minmax_i_update(m->last, m->last, m);
}
if (!UNDEF_P(m->min)) {
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 (!UNDEF_P(m->last_bv))
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;
} Возвращает, нет ли элемента, удовлетворяющего заданному критерию.
Без аргумента и без блока возвращает, нет ли истинного элемента:
(1..4).none? # => false
[nil, false].none? # => true
{foo: 0}.none? # => false
{foo: 0, bar: 1}.none? # => false
[].none? # => true
С аргументом pattern и без блока возвращает, нет ли элемента 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
С блоком возвращает, нет ли элемента, для которого блок возвращает истинное значение:
(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 (UNDEF_P(result)) return Qfalse;
return result;
} Возвращает, есть ли ровно один элемент, удовлетворяющий заданному критерию.
Без аргумента и без блока возвращает, есть ли ровно один истинный элемент:
(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 и без блока возвращает, есть ли ровно один элемент 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
С блоком возвращает, есть ли ровно один элемент, для которого блок возвращает истинное значение:
(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.
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 и блоком.
Если pattern === elt возвращает true или блок возвращает true для элемента, элемент считается концом разбиения.
Перечислитель и блок вызываются для каждого элемента перечисления enum.
Результат перечислителя возвращает разбитые элементы в виде массива. Поэтому метод each может вызываться следующим образом:
enum.slice_after(pattern).each { |ary| ... }
enum.slice_after { |elt| bool }.each { |ary| ... } Другие методы класса Enumerator и модуля Enumerable, такие как map, и т.д., также могут быть использованы.
Например, строки продолжения (строки, заканчивающиеся обратной косой чертой) могут быть объединены следующим образом:
lines = ["foo\n", "bar\\\n", "baz\n", "\n", "qux\n"]
e = lines.slice_after(/(?<!\\)\n\z/)
p e.to_a
#=> [["foo\n"], ["bar\\\n", "baz\n"], ["\n"], ["qux\n"]]
p e.map {|ll| ll[0...-1].map {|l| l.sub(/\\\n\z/, "") }.join + ll.last }
#=>["foo\n", "barbaz\n", "\n", "qux\n"]
static VALUE
enum_slice_before(int argc, VALUE *argv, VALUE enumerable)
{
VALUE enumerator;
if (rb_block_given_p()) {
if (argc != 0)
rb_error_arity(argc, 0, 0);
enumerator = rb_obj_alloc(rb_cEnumerator);
rb_ivar_set(enumerator, 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;
} Создаёт перечислитель для каждого разбиения элементов. Начало разбиений определяется блоком.
Этот метод разбивает каждый кусок, используя смежные элементы elt_before и elt_after в перечислителе получателя. Этот метод разбивает куски между elt_before и elt_after, где блок возвращает true.
Блок вызывается для каждого элемента, кроме последнего.
Результат перечислителя возвращает разбитые элементы в виде массива. Поэтому метод each может вызываться следующим образом:
enum.slice_when { |elt_before, elt_after| bool }.each { |ary| ... } Другие методы класса Enumerator и модуля Enumerable, такие как to_a, map, и т.д., также могут быть использованы.
Например, возрастающую последовательность можно разбить следующим образом:
a = [1,2,4,9,10,11,12,15,16,19,20,21]
b = a.slice_when {|i, j| i+1 != j }
p b.to_a #=> [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
c = b.map {|a| a.length < 3 ? a : "#{a.first}-#{a.last}" }
p c #=> [[1, 2], [4], "9-12", [15, 16], "19-21"]
d = c.join(",")
p d #=> "1,2,4,9-12,15,16,19-21"
Возле элементов (порог: 6) в отсортированном массиве можно разбить следующим образом:
a = [3, 11, 14, 25, 28, 29, 29, 41, 55, 57]
p a.slice_when {|i, j| 6 < j - i }.to_a
#=> [[3], [11, 14], [25, 28, 29, 29], [41], [55, 57]]
Возрастающую (неубывающую) последовательность можно разбить следующим образом:
a = [0, 9, 2, 2, 3, 2, 7, 5, 9, 5]
p a.slice_when {|i, j| i > j }.to_a
#=> [[0, 9], [2, 2, 3], [2, 7], [5, 9], [5]]
Последовательные чётные и нечётные числа можно разбить следующим образом: (Enumerable#chunk — ещё один способ сделать это.)
a = [7, 5, 9, 2, 0, 7, 9, 4, 2, 0]
p a.slice_when {|i, j| i.even? != j.even? }.to_a
#=> [[7, 5, 9], [2, 0], [7, 9], [4, 2, 0]]
Абзацы (непустые строки с последующими пустыми строками) можно разбить следующим образом: (См. Enumerable#chunk для пропуска пустых строк.)
lines = ["foo\n", "bar\n", "\n", "baz\n", "qux\n"]
p lines.slice_when {|l1, l2| /\A\s*\z/ =~ l1 && /\S/ =~ l2 }.to_a
#=> [["foo\n", "bar\n", "\n"], ["baz\n", "qux\n"]]
Enumerable#chunk_while делает то же самое, за исключением разделения, когда блок возвращает false вместо true.
static VALUE
enum_sort(VALUE obj)
{
return rb_ary_sort_bang(enum_to_a(0, 0, obj));
} Возвращает массив, содержащий отсортированные элементы 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_hidden_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;
data->primitive_uniformed = SORT_BY_UNIFORMED((CMP_OPTIMIZABLE(FLOAT) && CMP_OPTIMIZABLE(INTEGER)),
CMP_OPTIMIZABLE(FLOAT),
CMP_OPTIMIZABLE(INTEGER));
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) {
if (data->primitive_uniformed) {
RARRAY_PTR_USE(ary, ptr,
rb_uniform_intro_sort_2((struct rb_uniform_sort_data*)ptr,
(struct rb_uniform_sort_data*)(ptr + RARRAY_LEN(ary))));
}
else {
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 (!UNDEF_P(memo.r)) {
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_to_hash_type(argv[0]);
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]
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 847 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 имеют одинаковый размер, все элементы включаются в результат, и нет заполнения nil:
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.