Enumerable的group_by是否保留了Enumerable的顺序?

时间:2014-06-24 05:26:27

标签: arrays ruby enumerable

Enumerable#group_by是否会保留每个值中的原始订单?当我得到这个:

[1, 2, 3, 4, 5].group_by{|i| i % 2}
# => {1=>[1, 3, 5], 0=>[2, 4]}

是否可以保证,例如,数组[1, 3, 5]包含此顺序中的元素而不是,例如[3, 1, 5]

有关于这一点的描述吗?

我没有提及密钥10之间的顺序。这是一个不同的问题。

2 个答案:

答案 0 :(得分:12)

是的,Enumerable#group_by会保留输入订单。

这是MRI中该方法的实现,来自https://github.com/ruby/ruby/blob/trunk/enum.c

static VALUE
enum_group_by(VALUE obj)
{
    VALUE hash;

    RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);

    hash = rb_hash_new();
    rb_block_call(obj, id_each, 0, 0, group_by_i, hash);
    OBJ_INFECT(hash, obj);

    return hash;
}

static VALUE
group_by_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, hash))
{
    VALUE group;
    VALUE values;

    ENUM_WANT_SVALUE();

    group = rb_yield(i);
    values = rb_hash_aref(hash, group);
    if (!RB_TYPE_P(values, T_ARRAY)) {
        values = rb_ary_new3(1, i);
        rb_hash_aset(hash, group, values);
    }
    else {
        rb_ary_push(values, i);
    }
    return Qnil;
}

enum_group_by按顺序在每个数组(group_by_i)元素上调用objgroup_by_i在第一次遇到组时创建一个单元素数组(rb_ary_new3(1, i)),然后推送到数组(rb_ary_push(values, i))。因此保留了输入顺序。

此外,RubySpec需要它。来自https://github.com/rubyspec/rubyspec/blob/master/core/enumerable/group_by_spec.rb

it "returns a hash with values grouped according to the block" do
  e = EnumerableSpecs::Numerous.new("foo", "bar", "baz")
  h = e.group_by { |word| word[0..0].to_sym }
  h.should == { :f => ["foo"], :b => ["bar", "baz"]}
end

答案 1 :(得分:5)

更具体地说,Enumerable调用each因此取决于each的实施方式以及each是否按原始顺序生成元素:

class ReverseArray < Array
  def each(&block)
    reverse_each(&block)
  end
end

array = ReverseArray.new([1,2,3,4])
#=> [1, 2, 3, 4]

array.group_by { |i| i % 2 }
#=> {0=>[4, 2], 1=>[3, 1]}