How to convert, in Ruby, a list of key value pairs to a Hash, such that values with duplicate keys are stored in an array? - ruby

Given a list key-value pairs, in the form of an array of arrays - e.g. [ ["key1","value1"], ["key2","value2"], ["key1", "value3"] ], how to convert these to a Hash that stores all the values, in the most elegant way?
For the above example, I would want to get { "key1" => [ "value1", "value3" ], "key2" => [ "value2" ] }.

[["key1","value1"], ["key2","value2"], ["key1", "value3"]]
.group_by(&:first).each{|_, v| v.map!(&:last)}

Another way is to use the form of Hash#update (aka merge!) that uses a block to determine the values of keys that are in both hashes being merged.
arr = [ ["key1","value1"], ["key2","value2"], ["key1", "value3"] ]
arr.each_with_object({}) { |(k,v),h| h.update(k=>[v]) { |_,o,n| o+n } }
#=> {"key1"=>["value1", "value3"], "key2"=>["value2"]}

Hash.new{ |h,k| h[k]=[] }.tap{ |h| array.each{ |k,v| h[k] << v } }
OR
c = Hash.new {|h,k| h[k] = [] }
array.each{|k, v| c[k] << v}

My best solution so far is this:
kvlist.inject(Hash.new([])) do |memo,a|
memo[a[0]] = (memo[a[0]] << a[1])
memo
end
Which I think is not very good.

Related

ruby - iterate through a hash containing key/array value pairs, and iterate through each value

I have an hash that looks like this:
{
"key1": [
"value1",
"value2",
"value3"
],
"key2": [
"value1",
"value2",
"value3",
"value4",
"value5"
],
"key3": [
"value1"
],
"key4": [
"value1",
"value2"
]
}
How do I iterate through every keyN, while also looping through all the values in that key?
I have an array with all the keys if that helps.
Thanks
Pretty simple, really:
hash.each do |name, values|
values.each do |value|
# ...
end
end
You can do whatever you want with name and value at the lowest level.
If you are sure about the size of arrays, simply you can do like this,
ha = {:a => [1,2]}
ha.each do |k, (v1, v2)|
p k
p v1
p v2
end
Output
:a
1
2
You can do it like this:
hash.each do |key, array|
array.each do |value|
# do something
end
end
hash.each do |key_N, values_N|
values_N.each so |values|
.
.
#YourCode
.
.
end
end

ruby set key of hash for array of hashes

Given the following array of hashes, how can I make a new hash with friend_id as the key and dist the value?
results = [
{"user_id"=>"18", "friend_id"=>"17", "dist"=>"1"},
{"user_id"=>"18", "friend_id"=>"42", "dist"=>"1"},
{"user_id"=>"18", "friend_id"=>"43", "dist"=>"1"},
{"user_id"=>"18", "friend_id"=>"46", "dist"=>"2"}
]
desired_hash = {"17" => "1", "42" => "1", "43" => "1", "46" => "2"}
I've tried map but the values are then in an array. I also tried to flatten that result but it flattened the key instead of the value
results.each_with_object({}) { |g,h| h[g["friend_id"]] = g["dist"] }
or
results.each_with_object({}) { |g,h| h.update(g["friend_id"]=> g["dist"]) }
For this use case, I think it would be simpler and more readable to just use #each:
desired_hash = Hash.new
results.each {|h| desired_hash[h["friend_id"]] = h["dist"]}
Then, desired_hash is:
#=> {"17"=>"1", "42"=>"1", "43"=>"1", "46"=>"2"}
You can use Enumerable#inject method.
results.inject({}) {|sum, e| sum.merge({e["friend_id"] => e["dist"]})}
# => {"17"=>"1", "42"=>"1", "43"=>"1", "46"=>"2"}
results.map {|h| [h['friend_id'], h['dist']]} .to_h
Although I probably like #CarySwoveland 's answer better, on the lines of:
results.each_with_object({}) {|h, n| n[h['friend_id']] = h['dist']}
Simply:
desired_hash = Hash[results.map{ |h| [ h['friend_id'], h['dist']] }]
or as Victor suggests
desired_hash = Hash[results.map{ |x| x.values_at('friend_id', 'dist') }]

Add values of an array inside a hash

I created a hash that has an array as a value.
{
"0":[0,14,0,14],
"1":[0,14],
"2":[0,11,0,12],
"3":[0,11,0,12],
"4":[0,10,0,13,0,11],
"5":[0,10,0,14,0,0,0,11,12,0],
"6":[0,0,12],
"7":[],
"8":[0,14,0,12],
"9":[0,14,0,0,11,14],
"10":[0,11,0,12],
"11":[0,13,0,14]
}
I want the sum of all values in each array. I expect to have such output as:
{
"0":[24],
"1":[14],
"2":[23],
"3":[23],
"4":[34],
"5":[47],
"6":[12],
"7":[],
"8":[26],
"9":[39],
"10":[23],
"11":[27]
}
I do not know how to proceed from here. Any pointers are thankful.
I would do something like this:
hash = { "0" => [0,14,0,14], "1" => [0,14], "7" => [] }
hash.each { |k, v| hash[k] = Array(v.reduce(:+)) }
# => { "0" => [28], "1" => [14], "7" => [] }
For hash object you as this one.
hash = {"0"=>[0,14,0,14],"1"=>[0,14],"2"=>[0,11,0,12],"3"=>[0,11,0,12],"4"=>[0,10,0,13,0,11],"5"=>[0,10,0,14,0,0,0,11,12,0],"6"=>[0,0,12],"7"=>[],"8"=>[0,14,0,12],"9"=>[0,14,0,0,11,14],"10"=>[0,11,0,12],"11"=>[0,13,0,14]}
You could change value of each k => v pair
hash.each_pair do |k, v|
hash[k] = [v.reduce(0, :+)]
end
will resolve in
hash = {"0"=>[28], "1"=>[14], "2"=>[23], "3"=>[23], "4"=>[34], "5"=>[47], "6"=>[12], "7"=>[0], "8"=>[26], "9"=>[39], "10"=>[23], "11"=>[27]}
If your string is just like you mentioned you can parse it using JSON, or jump that step if you have already an Hash.
You can check the documentation of inject here
require 'json'
json_string = '
{
"0":[0,14,0,14],
"1":[0,14],
"2":[0,11,0,12],
"3":[0,11,0,12],
"4":[0,10,0,13,0,11],
"5":[0,10,0,14,0,0,0,11,12,0],
"6":[0,0,12],
"7":[],
"8":[0,14,0,12],
"9":[0,14,0,0,11,14],
"10":[0,11,0,12],
"11":[0,13,0,14]
}
'
hash = JSON.parse json_string
result = Hash.new
hash.each do |key, value|
result[key] = value.inject(:+)
end
puts result.inspect
and the result:
{"0"=>28, "1"=>14, "2"=>23, "3"=>23, "4"=>34, "5"=>47, "6"=>12, "7"=>nil, "8"=>26, "9"=>39, "10"=>23, "11"=>27}
Classic example for map/reduce. You need to iterate on the hash keys and values (map {|k,v|}), and for each value count the sum using reduce(0) {|acc, x| acc+x}
h = {1 => [1,2,3], 2 => [3,4,5], 7 => []} #example
a = h.map {|k,v| [k ,v.reduce(0) {|acc, x| acc+x}] }
=> [[1, 6], [2, 12], [7, 0]]
Hash[a]
=> {1=>6, 2=>12, 7=>0}

Convert multidimensional array to array of hashes

I have this data:
input = [ [ 'abc', '1.1' ], [ 'abc', '1.2' ], [ 'xyz', '3.14' ] ]
I would like output like the following:
[ { 'abc' => [ '1.1', '1.2' ] }, { 'xyz' => '3.14' } ]
Is it possible to achieve this in one chained expression?
I'd do it like this:
input = [['abc', '1.1'], ['abc','1.2'], ['xyz', '3.14']]
output = input.each_with_object(Hash.new{ |h, k| h[k] = [] }) { |(k, v), h| h[k] << v }
output # => {"abc"=>["1.1", "1.2"], "xyz"=>["3.14"]}
An alternate, which isn't as straightforward is:
input.group_by{ |k,v| k }.map{ |k, v| [k, v.map(&:last)] }.to_h # => {"abc"=>["1.1", "1.2"], "xyz"=>["3.14"]}
Your output structure
output: [{'abc' => ['1.1', '1.2']}, {'xyz' => '3.14'}]
is a very poor way to use a hash. Instead, you should have one hash, with multiple elements, since you're combining the like-key's values into one key.
If you REALLY need the output that way, as some seem to think, then simply append a map to the returned value:
input.each_with_object(Hash.new{ |h, k| h[k] = [] }) { |(k, v), h| h[k] << v }.map{ |k, v| {k => v} }
# => [{"abc"=>["1.1", "1.2"]}, {"xyz"=>["3.14"]}]
Simplest way I could think of:
input.group_by {|x| x[0]}.each_pair.map { |k, v| {k => (a=v.map(&:last)).count > 1 ? a : a[0]} }
The simplest way to do it is like this:
def convert_marray_to_hash(input)
hash = Hash.new { |hash, key| hash[key] = [] }
output = []
input.each { |array| hash[array[0]] << array[1] }
hash.keys.each { |key| output << { key => hash[key] } }
output
end
There are many ways to do it, but that way presents itself nicely and is readable.
This works pretty well:
input = [ [ 'abc', '1.1' ], [ 'abc','1.2' ], [ 'xyz', '3.14' ] ]
input.each_with_object({}) do |(key, val), hsh|
hsh[key] = val and next unless hsh.key?(key)
hsh[key] = [ *hsh[key], val ]
end
.map {|key, val| { key => val } }
# => [ { "abc" => [ "1.1", "1.2" ] },
# { "xyz" => "3.14" }
# ]
If you omit the final map you end up with a hash, which seems like a more natural result, but this matches the output specified, at least.

How to map a Ruby hash?

Is there a better way to map a Ruby hash? I want to iterate over each pair and collect the values. Perhaps using tap?
hash = { a:1, b:2 }
output = hash.to_a.map do |one_pair|
k = one_pair.first
v = one_pair.last
"#{ k }=#{ v*2 }"
end
>> [
[0] "a=2",
[1] "b=4"
]
Ruby's hash includes the Enumerable module which includes the map function.
hash = {a:1, b:2}
hash.map { |k, v| "#{k}=#{v * 2}" }
Enumerable#map | RubyDocs
Err, yes, with map, invoked directly on the hash:
{ a:1, b:2 }.map { |k,v| "#{k}=#{v*2}" } # => [ 'a=2', 'b=4' ]
hash = { a:1, b:2 }
hash.map{|k,v| [k,v*2]* '='}
# => ["a=2", "b=4"]

Resources