Reputation: 1
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.
Upvotes: 0
Views: 464
Reputation: 160571
The output structure isn't what I'd ever want to work with, and it looks like the input structure is influencing the desired output. That results in an XY problem.
Hashes are extremely efficient, especially when you have something that acts like an index field in a database. Iterating over an array to find a value is extremely inefficient in comparison to a hash, so I'd recommend taking a second look at those two structures.
Converting the input to a true hash isn't hard:
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"}
]
output = Hash.new { |h, k| h[k] = {} } # => {}
input.each { |e|
id = e['ID']
key = e['Key']
value = e['Value']
output[id][key] = value
}
Which results in:
output
# => {"100"=>{"Field A"=>"123", "Field B"=>"333", "Field C"=>"555"},
# "200"=>{"Field A"=>"789", "Field B"=>"999", "Field D"=>"444"}}
The benefit of this is pretty obvious, if you want the data for "200"
it's easy to grab:
output['200'] # => {"Field A"=>"789", "Field B"=>"999", "Field D"=>"444"}
output['200']['Field B'] # => "999"
Upvotes: 0
Reputation: 21130
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.
Upvotes: 2
Reputation: 110725
My answer has three steps.
Step 1: Obtain the unique values of "ID"
and the unique keys of the form "Field X"
ids, keys = input.map { |h| h.values_at("ID", "Key") }.transpose.map(&:uniq)
#=> [["100", "200"], ["Field A", "Field B", "Field C", "Field D"]]
See Hash#values_at. The calculations are as follows:
a = input.map { |h| h.values_at("ID", "Key") }
#=> [["100", "Field A"], ["100", "Field B"], ["100", "Field C"],
# ["200", "Field A"], ["200", "Field B"], ["200", "Field D"]]
b = a.transpose
#=> [["100", "100", "100", "200", "200", "200"],
# ["Field A", "Field B", "Field C", "Field A", "Field B", "Field D"]]
ids, keys = b.map(&:uniq)
#=> [["100", "200"], ["Field A", "Field B", "Field C", "Field D"]]
ids
#=> ["100", "200"]
keys
#=> ["Field A", "Field B", "Field C", "Field D"]
Step 2: Construct a hash whose keys are the unique values of "ID"
and whose values are hashes to be completed and extracted in Step 3
h = ids.each_with_object({}) { |id,h|
h[id] = keys.each_with_object("ID"=>id) { |key,g| g[key] = "" } }
#=> {"100"=>{"ID"=>"100", "Field A"=>"", "Field B"=>"", "Field C"=>"",
# "Field D"=>""},
# "200"=>{"ID"=>"200", "Field A"=>"", "Field B"=>"", "Field C"=>"",
# "Field D"=>""}}
Step 3: Loop through input
to complete the values of the hash constructed in Step 2, then, as a final step, extract the values from that hash
input.each_with_object(h) { |g,h| h[g["ID"]].update(g["Key"]=>g["Value"]) }.values
#=> [{"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"}]
See Hash#update (aka merge!
) and Hash#values. The two calculations are as follows:
h = input.each_with_object(h) { |g,h| h[g["ID"]].update(g["Key"]=>g["Value"]) }
#=> {"100"=>{"ID"=>"100", "Field A"=>"123", "Field B"=>"333","Field C"=>"555",
# "Field D"=>""},
# "200"=>{"ID"=>"200", "Field A"=>"789", "Field B"=>"999","Field C"=>"",
# "Field D"=>"444"}}
h.values
#=> <as above>
Upvotes: 1
Reputation: 1
keys = input.map { |hash| hash['Key'] }.uniq
output = input.group_by { |x| x['ID'] }.map { |k,v| ([['ID', k]] + v.map {|z| z.values_at('Key','Value') }).to_h }
output.map! { |x| {'ID' => x['ID']}.merge fields.map {|z| [z, x[z].to_s]}.to_h }
The following gives me the output as shown below
[
{"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"}
]
thanks you everyone for your input
Upvotes: -2
Reputation: 5552
Try Following,
fields = input.map {|x| x['Key'] }.uniq
output = input.group_by { |x| x['ID'] }
.map { |k,v| ([['ID', k]] + v.map {|z| z.values_at('Key','Value') }).to_h }
output.map! { |x| {'ID' => x['ID']}.merge fields.to_h {|z| [z, x[z].to_s]} }
Output will be,
[
{"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"}
]
Upvotes: 3