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.
Groupby preserves the order of rows within each group.
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.
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
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]}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With