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