Search code examples
rubyhashtable

How can I transpose array of hashes in ruby


I have a following array of hashes as the input :-

input =[
{"ID"=>"100", "Key"=>"Field A", "Value"=>"123"}, 
{"ID"=>"100", "Key"=>"Field B", "Value"=>"333"}, 
{"ID"=>"100", "Key"=>"Field C", "Value"=>"555"}, 
{"ID"=>"200", "Key"=>"Field A", "Value"=>"789"}, 
{"ID"=>"200", "Key"=>"Field B", "Value"=>"999"},
{"ID"=>"200", "Key"=>"Field D", "Value"=>"444"}
]

I would like to transform this array of hash as below

output =[
{"ID"=>"100", "Field A"=>"123", "Field B"=>"333", "Field C" => "555", "Field D" => ""}, 
{"ID"=>"200", "Field A"=>"789", "Field B"=>"999", "Field C" => "", "Field D" => "444"}
]

I can fetch unique ID and keys as below

irb(main):099:0> unique_id = input.map { |p| p["ID"] }.uniq
=> ["100", "200"]
irb(main):100:0> unique_keys = input.map { |p| p["Key"] }.uniq
=> ["Field A", "Field B", "Field C", "Field D"]

However, I am not able to proceed beyond this to create unique array of hashes for each ID containing keys/value pairs as defined on the input hash.


Solution

  • Something like this might do the job:

    keys = input.map { |hash| hash['Key'] }.uniq
    result = Hash.new { |result, id| result[id] = {} }
    input.each { |hash| result[hash['ID']].merge!(hash['Key'] => hash['Value']) }
    result.default = nil # optional: remove the default value
    
    result.each do |id, hash| 
      (keys - hash.keys).each { |key| hash[key] = '' }
      hash['ID'] = id
    end    
    
    result.values
    #=> [{"Field A"=>"123", "Field B"=>"333", "Field C"=>"555", "Field D"=>"", "ID"=>"100"},
    #    {"Field A"=>"789", "Field B"=>"999", "Field D"=>"444", "Field C"=>"", "ID"=>"200"}]
    

    If you're certain values are never falsy you can replace:

    (keys - hash.keys).each { |key| hash[key] = '' }
    # with
    keys.each { |key| hash[key] ||= '' }
    

    I first create a hash result to save the resulting hashes, I set the value to defaults to a new hash. Then I get the correct hash based upon ID and merge the key-value pairs into the hash. Lastly I add the missing keys to the hashes and set their values to an empty string and add the ID under which the hash is saved to the hash.

    note: If your input array contains duplicate key-value pairs, the last one will be used. For example, say both {"ID"=>"100", "Key"=>"Field A", "Value"=>"123"} and {"ID"=>"100", "Key"=>"Field A", "Value"=>"456"} are present. Then "Field A" => "456" will be set, since it's the latter of the two.