sawa sawa - 1 month ago 7x
Ruby Question

Does Enumerable's group_by preserve the Enumerable's order?


preserve the original order within each value? When I get this:

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

is it guaranteed that, for example, the array
[1, 3, 5]
contains the elements in this order and not, for example
[3, 1, 5]

Is there any description regarding this point?

I am not mentioning the order between the keys
. That is a different issue.


Yes, Enumerable#group_by preserves input order.

Here's the implementation of that method in MRI, from

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;


    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 calls group_by_i on each array (obj) element in order. group_by_i creates a one-element array (rb_ary_new3(1, i)) the first time a group is encountered, and pushes on to the array thereafter (rb_ary_push(values, i)). So the input order is preserved.

Also, RubySpec requires it. From

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