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

后端 未结 2 1306
闹比i
闹比i 2021-01-02 02:28

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条回答
  • 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
    
    0 讨论(0)
  • 2021-01-02 02:51

    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]}
    
    0 讨论(0)
提交回复
热议问题