模块 Enumerable

这里有什么

模块 Enumerable 提供了对集合类有用的方法,用于

查询方法

这些方法返回有关 Enumerable 的信息,而不是元素本身

  • include?, member?: 如果 self == object,则返回 true,否则返回 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 元素 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: 返回根据给定块指定的块组织的元素。

搜索和过滤方法

这些方法返回满足指定条件的元素

排序方法

这些方法返回按排序顺序排列的元素

  • 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

Ruby 类中的 Enumerable

这些 Ruby 核心类包含(或扩展)Enumerable

这些 Ruby 标准库类包含 Enumerable

  • CSV

  • CSV::Table

  • CSV::Row

  • Set

几乎所有 Enumerable 中的方法都调用包含类中的方法 #each

  • Hash#each 生成下一个键值对作为 2 元素 Array

  • Struct#each 生成下一个名称值对作为 2 元素 Array

  • 对于上面其他类,#each 生成集合中的下一个对象。

关于示例

Enumerable 方法的示例代码片段

  • 始终显示一个或多个类似数组类的使用(通常是 Array 本身)。

  • 有时会显示类似哈希类的使用。但是,对于某些方法,使用将没有意义,因此不会显示。示例:tally 将找到每个 Hash 条目的确切一个。

公共实例方法

all? → true 或 false 点击切换源代码
all?(pattern) → true 或 false
all? {|element| ... } → true 或 false

返回是否每个元素都满足给定的条件。

如果 self 没有元素,则返回 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 且没有块,返回是否对于每个元素 elementpattern === 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

相关:any?none? one?.

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;
}
any? → true 或 false 点击切换源代码
any?(pattern) → true 或 false
any? {|element| ... } → true 或 false

返回是否任何元素满足给定的条件。

如果 self 没有元素,则返回 false 并且不使用参数或块。

没有参数和块,返回是否任何元素都是真值。

(1..4).any?          # => true
%w[a b c d].any?     # => true
[1, false, nil].any? # => true
[].any?              # => false

带有参数 pattern 且没有块,返回是否对于任何元素 elementpattern === 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

相关:all?none?one?.

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;
}
chain(*enums) → 枚举器 点击切换源代码

返回由此枚举器和给定枚举器生成的枚举器对象。

e = (1..3).chain([4, 5])
e.to_a #=> [1, 2, 3, 4, 5]
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);
}
chunk {|array| ... } → 枚举器 点击切换源代码

返回的枚举器中的每个元素都是一个包含 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
  }
}

您可以使用特殊符号 :_separatornil 强制忽略元素(不包含在任何块中)。

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(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;
}
chunk_while {|elt_before, elt_after| bool } → an_enumerator 点击切换源代码

为每个块元素创建一个枚举器。块定义了块的开头。

此方法使用接收器枚举器中的相邻元素 elt_beforeelt_after 拆分每个块。此方法在块返回 false 时在 elt_beforeelt_after 之间拆分块。

块被调用接收器枚举器的长度减一。

结果枚举器将块元素作为数组生成。因此,可以按如下方式调用 each 方法。

enum.chunk_while { |elt_before, elt_after| bool }.each { |ary| ... }

Enumerator 类和 Enumerable 模块的其他方法,例如 to_amap 等,也可以使用。

例如,可以按如下方式对逐个递增的子序列进行分块。

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_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;
}
collect -> enumerator 点击切换源代码

返回块返回的对象数组。

如果给定块,则使用连续元素调用块;返回块返回的对象数组。

(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_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;
}
也称为:map
collect_concat()

返回由块返回的扁平化对象的数组。

如果给定块,则使用连续元素调用块;返回由块返回的扁平化对象的数组。

[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

别名:flat_map
compact → array 点击切换源代码

返回所有非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_compact(VALUE obj)
{
    VALUE ary;

    ary = rb_ary_new();
    rb_block_call(obj, id_each, 0, 0, compact_i, ary);

    return ary;
}
count → integer 点击切换源代码
count(object) → integer
count {|element| ... } → integer

返回元素的数量,基于给定的参数或块条件。

如果没有给出参数和块,则返回元素的数量。

[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_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);
}
cycle(n = nil) {|element| ...} → nil 点击切换源代码
cycle(n = nil) → enumerator

当使用正整数参数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]]

如果计数为零或负数,则不调用块。

当使用块调用且nnil时,无限循环。

如果没有给出块,则返回一个Enumerator

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;
}
detect(*args)

返回块返回真值的第一个元素。

如果给定块,则使用集合的连续元素调用块;返回块返回真值的第一个元素。

(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

别名:find
drop(n) → array 点击切换源代码

对于正整数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(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;
}
drop_while {|element| ... } → array 点击切换源代码
drop_while → enumerator

只要块返回真值,就用连续的元素调用块;返回该点之后的所有元素的数组

(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_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;
}
each_cons(n) { ... } → self 点击切换源代码
each_cons(n) → 枚举器

用每个连续的重叠的 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_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;
}
each_entry(*args) {|element| ... } → self 点击切换源代码
each_entry(*args) → 枚举器

用每个元素调用给定的块,将来自 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_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;
}
each_slice(n) { ... } → self 点击切换源代码
each_slice(n) → 枚举器

用每个连续的非重叠的 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_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;
}
each_with_index(*args) {|element, i| ..... } → self 点击切换源代码
each_with_index(*args) → 枚举器

如果给定块,则用每个元素及其索引调用块;返回 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_index(int argc, VALUE *argv, VALUE obj)
{
    struct MEMO *memo;

    RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size);

    memo = MEMO_NEW(0, 0, 0);
    rb_block_call(obj, id_each, argc, argv, each_with_index_i, (VALUE)memo);
    return obj;
}
each_with_object(object) { |(*args), memo_object| ... } → object 点击切换源代码
each_with_object(object) → 枚举器

对每个元素调用块一次,传递元素和给定的对象

(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_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;
}
entries(*args)

返回一个包含 self 中项目的数组

(0..4).to_a # => [0, 1, 2, 3, 4]
别名:to_a
filter()

返回一个包含由块选择的元素的数组。

如果给定块,则用连续的元素调用块;返回一个包含块返回真值的元素的数组

(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.

别名:find_all
filter_map {|element| ... } → array 点击切换源代码
filter_map → 枚举器

返回一个包含块返回的真值元素的数组。

如果给定块,则用连续的元素调用块;返回一个包含块返回的每个真值的数组

(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_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;
}
find(if_none_proc = nil) {|element| ... } → object or nil 点击切换源代码
find(if_none_proc = 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') } # => []

如果没有给定块,则返回一个 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;
}
别名:detect
find_all -> 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_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;
}
别名:select, filter
find_index(object) → integer or nil 点击切换源代码
find_index {|element| ... } → integer or nil
find_index → enumerator

返回第一个满足指定条件的元素的索引,如果找不到这样的元素,则返回 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_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;
}
first → element or nil 点击切换源代码
first(n) → array

返回第一个元素或元素。

如果没有给出参数,则返回第一个元素,如果没有元素则返回 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_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;
    }
}
flat_map {|element| ... } → array 点击切换源代码
flat_map → enumerator

返回由块返回的扁平化对象的数组。

如果给定块,则使用连续元素调用块;返回由块返回的扁平化对象的数组。

[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_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;
}
别名:collect_concat
grep(pattern) → array 点击切换源代码
grep(pattern) {|element| ... } → array

返回一个基于 self 的元素的数组,这些元素与给定的模式匹配。

如果没有给出块,则返回一个数组,其中包含每个 pattern === elementtrue 的元素。

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(VALUE obj, VALUE pat)
{
    return enum_grep0(obj, pat, Qtrue);
}
grep_v(pattern) → array 点击切换源代码
grep_v(pattern) {|element| ... } → array

返回一个对象数组,该数组基于self中的不匹配给定模式的元素。

如果没有给出代码块,则返回一个数组,其中包含每个pattern === elementfalse的元素。

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_grep_v(VALUE obj, VALUE pat)
{
    return enum_grep0(obj, pat, Qfalse);
}
group_by {|element| ... } → hash 点击切换源代码
group_by → enumerator

如果给出代码块,则返回一个哈希表。

  • 每个键都是代码块的返回值。

  • 每个值都是一个数组,其中包含代码块返回该键的那些元素。

示例

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

static VALUE
enum_group_by(VALUE obj)
{
    RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);

    return enum_hashify(obj, 0, 0, group_by_i);
}
include?(object) → 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
别名:member?
inject(symbol) → object 点击切换源代码
inject(initial_operand, symbol) → object
inject {|memo, operand| ... } → object
inject(initial_operand) {|memo, operand| ... } → object

返回一个由操作数形成的对象,通过以下方式之一:

  • 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
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;
}
也称为:reduce
lazy → lazy_enumerator 点击切换源代码

返回一个 Enumerator::Lazy,它重新定义了大多数 Enumerable 方法,以推迟枚举,并且仅在需要时枚举值。

示例

以下程序查找勾股数。

def pythagorean_triples
  (1..Float::INFINITY).lazy.flat_map {|z|
    (1..z).flat_map {|x|
      (x..z).select {|y|
        x**2 + y**2 == z**2
      }.map {|y|
        [x, y, z]
      }
    }
  }
end
# show first ten pythagorean triples
p pythagorean_triples.take(10).force # take is lazy, so force is needed
p pythagorean_triples.first(10)      # first is eager
# show pythagorean triples less than 100
p pythagorean_triples.take_while { |*, z| z < 100 }.force
static VALUE
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;
}
map {|element| ... } → array
map → enumerator

返回块返回的对象数组。

如果给定块,则使用连续元素调用块;返回块返回的对象数组。

(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

别名:collect
max → element 点击切换源代码
max(n) → array
max {|a, b| ... } → element
max(n) {|a, b| ... } → array

根据给定的标准返回具有最大元素的元素。相等元素的排序是不确定的,并且可能不稳定。

如果没有参数和块,则返回最大元素,使用元素自己的方法 <=> 进行比较。

(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)                       # => []

如果给定了块,则块将确定最大元素。块将使用两个元素 ab 调用,并且必须返回

  • 如果 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 }                          # => []

相关:minminmaxmax_by

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;
}
max_by {|element| ... } → element 点击切换源代码
max_by(n) {|element| ... } → array
max_by → enumerator
max_by(n) → enumerator

返回代码块返回最大值的元素。

如果给定一个代码块且没有参数,则返回代码块返回最大值的元素。

(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

相关:maxminmaxmin_by

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;
}
member?(object) -> true or 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_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;
}
别名:include?
min → element 点击切换源代码
min(n) → array
min {|a, b| ... } → element
min(n) {|a, b| ... } → array

返回根据给定标准具有最小元素的元素。相等元素的排序是不确定的,并且可能不稳定。

如果没有参数和代码块,则返回最小元素,使用元素自身的 <=> 方法进行比较。

(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)                       # => []

如果给定一个代码块,则代码块确定最小元素。代码块用两个元素 ab 调用,并且必须返回

  • 如果 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 }                          # => []

相关:min_byminmaxmax

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;
}
min_by {|element| ... } → element 点击切换源代码
min_by(n) {|element| ... } → array
min_by → enumerator
min_by(n) → enumerator

返回块返回值最小的元素。

如果给定块且没有参数,则返回块返回值最小的元素。

(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

相关:minminmaxmax_by

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;
}
minmax → [minimum, maximum] 点击切换源代码
minmax {|a, b| ... } → [minimum, maximum]

返回一个包含最小和最大元素的 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]

相关:minmaxminmax_by.

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);
}
minmax_by {|element| ... } → [minimum, maximum] 点击切换源代码
minmax_by → enumerator

返回一个包含块返回值最小和最大元素的 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

相关:max_byminmaxmin_by.

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);
}
none? → true or false 点击切换源代码
none?(pattern) → true or false
none? {|element| ... } → true or false

返回是否没有元素满足给定的条件。

如果没有参数和块,则返回是否没有元素为真值。

(1..4).none?           # => false
[nil, false].none?     # => true
{foo: 0}.none?         # => false
{foo: 0, bar: 1}.none? # => false
[].none?               # => true

如果给定参数 pattern 且没有块,则返回是否对于没有元素 elementpattern === 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

相关:one?all?any?.

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;
}
one? → true 或 false 点击切换源代码
one?(pattern) → true 或 false
one? {|element| ... } → true 或 false

返回是否正好有一个元素满足给定条件。

如果没有参数和块,则返回是否正好有一个元素为真值。

(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

相关:none?all?any?.

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;
}
partition {|element| ... } → [true_array, false_array] 点击切换源代码
partition → 枚举器

如果给定块,则返回两个数组的数组。

  • 第一个数组包含块返回真值的元素。

  • 另一个数组包含所有其他元素。

示例

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_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);
}
reduce(p1 = v1, p2 = v2)

返回一个由操作数形成的对象,通过以下方式之一:

  • 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"
别名:inject
reject {|element| ... } → array 点击切换源代码
reject → 枚举器

返回被块拒绝的元素数组。

如果给定块,则使用连续的元素调用块;返回块返回 nilfalse 的元素数组。

(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_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;
}
reverse_each(*args) {|element| ... } → self 点击切换源代码
reverse_each(*args) → 枚举器

如果给定块,则使用每个元素调用块,但顺序相反;返回 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

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;
}
select {|element| ... } → array
select → 枚举器

返回一个包含由块选择的元素的数组。

如果给定块,则用连续的元素调用块;返回一个包含块返回真值的元素的数组

(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.

别名:find_all
slice_after(pattern) → 枚举器 点击切换源代码
slice_after { |elt| bool } → 枚举器

为每个分块元素创建枚举器。块和 pattern 定义块的结束位置。

如果 pattern === elt 返回 true 或块对元素返回 true,则该元素为块的结束位置。

===blockenum 的第一个元素调用到最后一个元素。

结果枚举器将块元素作为数组生成。因此,可以按如下方式调用 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_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;
}
slice_before(pattern) → enumerator 点击切换源代码
slice_before {|elt| ... } → 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_amap 等,也可以使用。

例如,对 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_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;
}
slice_when {|elt_before, elt_after| bool } → an_enumerator 点击切换源代码

为每个块元素创建一个枚举器。块定义了块的开头。

此方法使用接收器枚举器中的相邻元素 elt_beforeelt_after 来拆分每个块。此方法在代码块返回 trueelt_beforeelt_after 之间拆分块。

块被调用接收器枚举器的长度减一。

结果枚举器将块元素作为数组生成。因此,可以按如下方式调用 each 方法。

enum.slice_when { |elt_before, elt_after| bool }.each { |ary| ... }

Enumerator 类和 Enumerable 模块的其他方法,例如 to_amap 等,也可以使用。

例如,可以按如下方式对逐个递增的子序列进行分块。

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_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;
}
sort → array 点击切换源代码
sort {|a, b| ... } → array

返回一个包含self中已排序元素的数组。相等元素的排序顺序是不确定的,并且可能不稳定。

如果没有给出代码块,则排序使用元素自身的<=>方法进行比较。

%w[b c a d].sort              # => ["a", "b", "c", "d"]
{foo: 0, bar: 1, baz: 2}.sort # => [[:bar, 1], [:baz, 2], [:foo, 0]]

如果给出代码块,则代码块中的比较将决定排序顺序。代码块将使用两个元素ab调用,并且必须返回

  • 如果 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。它实现了 Schwartzian 变换,当键计算或比较很昂贵时很有用。

static VALUE
enum_sort(VALUE obj)
{
    return rb_ary_sort_bang(enum_to_a(0, 0, obj));
}
sort_by {|element| ... } → array 点击切换源代码
sort_by → 枚举器

如果给出代码块,则返回一个包含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 用户通常将这种方法称为 Schwartzian 变换,以 Randal Schwartz 命名。我们构造一个临时数组,其中每个元素都是一个包含排序键和文件名数组的数组。我们对这个数组进行排序,然后从结果中提取文件名。

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_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;
}
sum(initial_value = 0) → number 点击切换源代码
sum(initial_value = 0) {|element| ... } → object

如果没有给出代码块,则返回 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_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;
    }
}
take(n) → array 点击切换源代码

对于非负整数 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(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;
}
take_while {|element| ... } → array 点击切换源代码
take_while → enumerator

只要代码块返回真值,就使用连续的元素调用代码块;返回所有元素的数组,直到该点。

(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_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;
}
tally → new_hash 点击切换源代码
tally(hash) → 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_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);
}
to_a(*args) → array 点击切换源代码

返回一个包含 self 中项目的数组

(0..4).to_a # => [0, 1, 2, 3, 4]
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;
}
也称为:entries
to_h(*args) → hash 点击切换源代码
to_h(*args) {|element| ... } → hash

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 元素数组,并且没有传递代码块,则会引发异常。

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);
}
to_set(klass = Set, *args, &block) 点击切换源代码

使用给定参数从可枚举对象创建集合。

# File ruby_3_3_0/prelude.rb, line 28
def to_set(klass = Set, *args, &block)
  klass.new(self, *args, &block)
end
uniq → array 点击切换源代码
uniq {|element| ... } → array

如果没有块,则返回一个新数组,其中只包含唯一元素;该数组没有两个元素 e0e1 使得 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_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;
}
zip(*other_enums) → array 点击切换源代码
zip(*other_enums) {|array| ... } → nil

如果没有给出块,则返回一个大小为 self.size 的新数组 new_array,其元素为数组。每个嵌套数组 new_array[n] 的大小为 other_enums.size+1,并且包含

  • self 的第 n 个元素。

  • 每个 other_enums 的第 n 个元素。

如果所有 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,则用 nil 填充到 self.size

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]]

当给出块时,使用每个子数组(如上所述形成)调用块;返回 nil

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]
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;
}