Convert array of 2-element arrays into a hash, where duplicate keys append additional values

后端 未结 4 973
感动是毒
感动是毒 2020-12-01 05:03

For example

Given an array:

array = [[:a,:b],[:a,:c],[:c,:b]]

Return the following hash:

hash = { :a => [:b,:c         


        
相关标签:
4条回答
  • 2020-12-01 05:38

    Using functional baby steps:

    irb:01.0> array = [[:a,:b],[:a,:c],[:c,:b]]
    #=> [[:a, :b], [:a, :c], [:c, :b]]
    
    irb:02.0> array.group_by(&:first)
    #=> {:a=>[[:a, :b], [:a, :c]], :c=>[[:c, :b]]}
    
    irb:03.0> array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] }
    #=> [[:a, [:b, :c]], [:c, [:b]]]
    
    irb:04.0> Hash[ array.group_by(&:first).map{ |k,a| [k,a.map(&:last)] } ]
    #=> {:a=>[:b, :c], :c=>[:b]}
    

    Using imperative style programming:

    irb:10.0> h = Hash.new{ |h,k| h[k]=[] }
    #=> {}
    
    irb:11.0> array.each{ |k,v| h[k] << v }
    #=> [[:a, :b], [:a, :c], [:c, :b]]
    
    irb:12.0> h
    #=> {:a=>[:b, :c], :c=>[:b]}
    

    As an imperative one-liner:

    irb:13.0> h = Hash.new{ |h,k| h[k]=[] }.tap{ |h| array.each{ |k,v| h[k] << v } }
    #=> {:a=>[:b, :c], :c=>[:b]}
    

    Or using everyone's favorite inject:

    irb:14.0> array.inject(Hash.new{ |h,k| h[k]=[] }){ |h,(k,v)| h[k] << v; h }
    #=> {:a=>[:b, :c], :c=>[:b]}
    

    If you really want to have single values not collided as an array, you can either un-array them as a post-processing step, or use a different hash accumulation strategy that only creates an array upon collision. Alternatively, wrap your head around this:

    irb:17.0> hashes = array.map{ |pair| Hash[*pair] } # merge many mini hashes
    #=> [{:a=>:b}, {:a=>:c}, {:c=>:b}]
    
    irb:18.0> hashes.inject{ |h1,h2| h1.merge(h2){ |*a| a[1,2] } }
    #=> {:a=>[:b, :c], :c=>:b}
    
    0 讨论(0)
  • 2020-12-01 05:41

    EDIT: In Ruby 2.1+, you can use Array#to_h

    pry(main)> [[:a,:b],[:a,:c],[:c,:b]].to_h
    => {:a=>:c, :c=>:b}
    

    END EDIT

    The public [] method on the Hash class accepts a key-value pair array and returns a hash with the first element of the array as key and the second as value.

    The last value in the key-value pair will be the actual value when there are key duplicates.

    Hash[[[:a,:b],[:a,:c],[:c,:b]]]
        => {:a=>:c, :c=>:b}
    

    This syntax is valid in 1.9.3+ ; I'm not sure about earlier Ruby versions (it's not valid in 1.8.7)

    ref: http://www.ruby-doc.org/core-2.1.0/Hash.html#method-c-5B-5D

    Another interesting way of doing it would be using the inject method: (obviously the method above is more succinct and recommended for this specific problem)

    [ [:a, :b], [:a, :c], [:c, :b] ].inject({}) { |memo, obj| 
       memo[obj.first] = obj.last
       memo 
    }
    
    => {:a=>:c, :c=>:b}
    

    inject iterates over the enumerable, your array in this case, starting with the injected parameter, in this case the empty hash {}.

    For each object in the enumerable, the block is called with the variables memo and obj:

    • obj is the current object in the array

    • memo is the value that has been returned by your block's last iteration (for the first iteration, it's what you inject)

    0 讨论(0)
  • 2020-12-01 05:47

    This kind of operations is very common in our project, so we added to_group_h to Enumerable. We can use it like:

    [[:x, 1], [:x, 2], [:y, 3]].to_h
    # => { x: 2, y: 3 }
    
    [[:x, 1], [:x, 2], [:y, 3]].to_group_h
    # => { x: [1, 2], y: [3] }
    

    The following is the implementation of Enumerable#to_group_h:

    module Enumerable
      if method_defined?(:to_group_h)
        warn 'Enumerable#to_group_h is defined'
      else
        def to_group_h
          hash = {}
          each do |key, value|
            hash[key] ||= []
            hash[key] << value
          end
          return hash
        end
      end
    end
    
    0 讨论(0)
  • 2020-12-01 06:03

    This can be done fairly succinctly using each_with_object.

    array.each_with_object({}) { |(k, v), h| h[k] = (h[k] || []) + [v] }
    

    Demonstrating in irb:

    irb(main):002:0> array = [[:a,:b],[:a,:c],[:c,:b]]
    => [[:a, :b], [:a, :c], [:c, :b]]
    irb(main):003:0> array.each_with_object({}) { |(k, v), h| h[k] = (h[k] || []) + [v] }
    => {:a=>[:b, :c], :c=>[:b]}
    
    0 讨论(0)
提交回复
热议问题