是的,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 按顺序在每个数组 (obj) 元素上调用 group_by_i。 group_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