Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Does Enumerable#group_by 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 1 and 0. That is a different issue.

like image 844
sawa Avatar asked Jun 24 '14 05:06

sawa


People also ask

Does group by maintain order?

Groupby preserves the order of rows within each group.

What does group_ by do in ruby?

The group_by() of enumerable is an inbuilt method in Ruby returns an hash where the groups are collectively kept as the result of the block after grouping them. In case no block is given, then an enumerator is returned. Parameters: The function takes an optional block according to which grouping is done.


2 Answers

Yes, Enumerable#group_by preserves input order.

Here's the implementation of that method in MRI, from 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 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 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
like image 147
Dave Schweisguth Avatar answered Sep 18 '22 21:09

Dave Schweisguth


More specifically, Enumerable calls each so it depends on how each is implemented and whether each yields the elements in the original order:

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]}
like image 30
Stefan Avatar answered Sep 20 '22 21:09

Stefan