How to merge array of hash based on the same keys in ruby? - ruby

How to merge array of hash based on the same keys in ruby?
example :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
How to get result like this?
a = [{:a=>[1, 10]},{:b=>8},{:c=>[7, 2]}]

Try
a.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.map(&:last)]}

Another alternative:
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
p a.each_with_object({}) { |h, o| h.each { |k,v| (o[k] ||= []) << v } }
# => {:a=>[1, 10], :b=>[8], :c=>[7, 2]}
It also works when the Hashes have multiple key/value combinations, e.g:
b = [{:a=>1, :b=>5, :x=>10},{:a=>10, :y=>2},{:b=>8},{:c=>7},{:c=>2}]
p b.each_with_object({}) { |h, o| h.each { |k,v| (o[k] ||= []) << v } }
# => {:a=>[1, 10], :b=>[5, 8], :x=>[10], :y=>[2], :c=>[7, 2]}

Minor addition to answer by Arie Shaw to match required answer:
a.flat_map(&:entries)
.group_by(&:first)
.map{|k,v| Hash[k, v.size.eql?(1) ? v.last.last : v.map(&:last) ]}
#=> [{:a=>[1, 10]}, {:b=>8}, {:c=>[7, 2]}]

I'd do :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
merged_hash = a.each_with_object({}) do |item,hsh|
k,v = item.shift
hsh[k] = hsh.has_key?(k) ? [ *Array( v ), hsh[k] ] : v
end
merged_hash.map { |k,v| { k => v } }
# => [{:a=>[10, 1]}, {:b=>8}, {:c=>[2, 7]}]
update
A better taste :
a = [{:a=>1},{:a=>10},{:b=>8},{:c=>7},{:c=>2}]
merged_hash = a.each_with_object({}) do |item,hsh|
k,v = item.shift
(hsh[k] ||= []) << v
end
merged_hash.map { |k,v| { k => v } }
# => [{:a=>[10, 1]}, {:b=>8}, {:c=>[2, 7]}]

Related

transform keys and values with an array as one of them

I have the following Ruby hash
a = {
7 => [1469, 2283],
5 => [1469, 5464],
3 => [7561],
6 => [7952, 8114],
4 => []
}
and would like to get the keys that a number shows up in as a value
b = {
1469 => [7,5],
2283 => [7],
5464 => [5]
...
}
How would I do this? I'm sure there's some super slick way of getting it done.
Given:
a = {
7 => [1469, 2283],
5 => [1469, 5464],
3 => [7561],
6 => [7952, 8114],
4 => [1469, 2283],
2 => []
}
Use a default value of a new array in b:
b=Hash.new {|hsh, key| hsh[key] = [] }
a.each{|k,v| v.each{|n| b[n] << k} }
Or include the object creation with each_with_object:
b=a.each_with_object(Hash.new {|h,k| h[k] = []}) { |(k,v), h|
v.each{ |e| h[e] << k } }
Result b is:
{1469=>[7, 5, 4], 2283=>[7, 4], 5464=>[5], 7561=>[3], 7952=>[6], 8114=>[6]}
Using #each_with_object to iterate over the original hash and the values stored in each array and build up a new hash based on it.
a.each_with_object({}) do |(k, v), h|
v.each do |val|
h[val] ||= []
h[val] << k
end
end
Result:
{
1469 => [7, 5],
2283 => [7],
5464 => [5],
7561 => [3],
7952 => [6],
8114 => [6]
}
#dawg's answer is the way I would go
a.each_with_object(Hash.new {|h,k| h[k] = []}) do |(k,v),obj|
v.each{|e| obj[e] << k}
end
But here are a few other options: (a lot of allocations though)
Array#product and Hash#merge
a.each_with_object({}) do |(k,v),obj|
obj.merge!(v.product([[k]]).to_h) {|_,o,v| [*o,*v]}
end
#map then #reduce
a.map {|k,v| v.product([[k]]).to_h }
.reduce {|memo, h| memo.merge(h) {|_,o,n| o.concat(n)}}
This works too (don't ask questions)
a.map {|k,v| v.each_with_object(k)}
.reduce(&:+)
.group_by(&:shift)
.transform_values(&:flatten)

about creating hash with certain keys

I am lost. I need to create a hash with keys corresponding to numbers from 1 to 10. The result should be something like this:
my_hash = {1 => "", 2 => "", 3 => ""...}
I have
h = Hash.new
please give me at least one way of doing that, I will need "values" later, for now I just need a hash with keys and I will push values later. Thanks!
h = Hash.new
(1..10).each {|count| h[count] = ''}
What about:
(1..10).map { |x| [x, ''] }.to_h
Or:
{}.tap { |h| 1.step(10) { |i| h[i] = '' } }
Or:
(1..10).zip(Array.new(10) { '' }).to_h
A few more options:
(1..10).each_with_object({}) { |i, h| h[i] = '' }
10.times.each_with_object({}) { |i, h| h[i + 1] = '' }
1.upto(10).each_with_object({}) { |i, h| h[i] = '' }
# Or if you think each_with_object is heresy...
(1..10).inject({}) { |h, i| h.merge!(i => '') } # merge! to avoid wasteful copying
10.times.inject({}) { |h, i| h.merge!(i + 1 => '') }
1.upto(10).inject({}) { |h, i| h.merge!(i => '') }

Conversion of Ruby Hashes to Sort array of Hash

I have following input:
array = [{:year=>2015, :platform_id=>2},
{:year=>nil, :platform_id=>2},
{:year=>nil, :platform_id=>4},
{:year=>2015, :platform_id=>4}]
I need expected result to be:
[{platform_id=>2, year=>[2015, nil]},
{platform_id=>4, year=>[nil, 2015]}]
What I code is:
array.inject(:merge)
But that gets me this result, which is not what I want:
{:year=>2015, :platform_id=>4}
Updated below after answer received:
I do performance test after see the answers and here is the result:
arr = [
{:year => 2015, :platform_id => 2},
{:year => nil, :platform_id => 2},
{:year => nil, :platform_id => 4},
{:year => 2015, :platform_id => 4}
]
#approach 1
x1 = Time.now.to_f
exp = arr.each_with_object({}) do |h, exp|
exp[h[:platform_id]] ||= {:platform_id => h[:platform_id], :year => []}
exp[h[:platform_id]][:year] << h[:year]
end.values
x2 = Time.now.to_f
p x2-x1
#approach 2
x3 = Time.now.to_f
new_data = arr.group_by { |d| d[:platform_id] }
new_arr = []
new_data.each do |k,v|
t2 = v.map{|x| x[:year]}
temp = {"platform_id": k, "years": t2}
new_arr.push(temp)
end
x4 = Time.now.to_f
p x4-x3
#approach 3
x5 = Time.now.to_f
f = arr.each_with_object({}) { |g,h|
h.update(g[:platform_id]=>[g[:year]]) { |_,o,n| o+n } }
#=> {2=>[2015, nil], 4=>[nil, 2015]}
f.map { |k,v| { :platform_id=>k, :year=>v } }
x6 = Time.now.to_f
p x6-x5
#output is:
9.059906005859375e-06
6.4373016357421875e-06
9.775161743164062e-06
arr = [
{ :year=>2015, :platform_id=>2 },
{ :year=>nil, :platform_id=>2 },
{ :year=>nil, :platform_id=>4 },
{ :year=>2015, :platform_id=>4 }
]
arr.each_with_object({}) { |g,h|
h.update(g[:platform_id]=>[g[:year]]) { |_,o,n| o+n } }.
map { |k,v| { :platform_id=>k, :year=>v } }
#=> [{:platform_id=>2, :year=>[2015, nil]},
# {:platform_id=>4, :year=>[nil, 2015]}]
The two steps are as follows.
f = arr.each_with_object({}) { |g,h|
h.update(g[:platform_id]=>[g[:year]]) { |_,o,n| o+n } }
#=> {2=>[2015, nil], 4=>[nil, 2015]}
f.map { |k,v| { :platform_id=>k, :year=>v } }
#=> [{:platform_id=>2, :year=>[2015, nil]},
# {:platform_id=>4, :year=>[nil, 2015]}]
The first step uses the form of Hash#update (aka merge!) that employs a block (here { |_,o,n| o+n }) that computes the value of keys that are present in both hashes being merged.
You can use each_with_object with hash and print values like this
arr = [
{:year => 2015, :platform_id => 2},
{:year => nil, :platform_id => 2},
{:year => nil, :platform_id => 4},
{:year => 2015, :platform_id => 4}
]
exp = arr.each_with_object({}) do |h, exp|
exp[h[:platform_id]] ||= {:platform_id => h[:platform_id], :year => []}
exp[h[:platform_id]][:year] << h[:year]
end.values
p exp
# => [{:platform_id=>2, :year=>[2015, nil]}, {:platform_id=>4, :year=>[nil, 2015]}]
Here is the answer:
data = [
{:year=>2015, :platform_id=>2},
{:year=>nil, :platform_id=>2},
{:year=>nil, :platform_id=>4},
{:year=>2015, :platform_id=>4}
]
new_data = data.group_by { |d| d[:platform_id] }
#p new_data
new_arr = []
new_data.each do |k,v|
t2 = v.map{|x| x[:year]}
temp = {"pf_id": k, "years": t2}
new_arr.push(temp)
end
p new_arr

Combine hash keys and values if keys are identical except for case

Let's say I have a Ruby Hash where at least two keys are identical, except for their case, for instance:
{ 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
Is there a way I can combine like keys (except for their case) such that the resulting Hash might look like this?
{ 'foo' => 3, 'bar' => 3 }
Thank you!
You can build a new hash:
new_hash = Hash.new(0)
old_hash.each_pair { |k, v| new_hash[k.downcase] += v }
You can use inject to loop all the hash items and build a new hash.
hash = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
hash.inject({}) do |result, (key, value)|
key = key.downcase
result[key] = result[key] ? result[key] + value : value
result
end
Here is one more way of doing this
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
p h.collect{|k, v| {k.downcase => v}}.reduce { |a, v| a.update(v) {|k, o, n| o + n } }
#=> {"foo"=>3, "bar"=>3}
h = { 'Foo' => 1, 'foo' => 2, 'bar' => 3 }
h.each_with_object({}) { |(k,v),g| g[k.downcase] = g[k.downcase].to_i + v }
#=> {"foo"=>3, "bar"=>3}
This makes use of the fact that if g does not have a key e, g[e] on the right side will equal nil and nil.to_i #=> 0. On the other hand, if g has a key e, h[e].to_i will equal h[e].
Another way:
h.each_with_object({}) { |(k,v),g| g.update(k.downcase=>v) { |_,o,v| o+v } }
#=> {"foo"=>3, "bar"=>3}

Ruby: Get all keys in a hash (including sub keys)

let's have this hash:
hash = {"a" => 1, "b" => {"c" => 3}}
hash.get_all_keys
=> ["a", "b", "c"]
how can i get all keys since hash.keys returns just ["a", "b"]
This will give you an array of all the keys for any level of nesting.
def get_em(h)
h.each_with_object([]) do |(k,v),keys|
keys << k
keys.concat(get_em(v)) if v.is_a? Hash
end
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
get_em(hash) # => ["a", "b", "c", "d"]
I find grep useful here:
def get_keys(hash)
( hash.keys + hash.values.grep(Hash){|sub_hash| get_keys(sub_hash) } ).flatten
end
p get_keys my_nested_hash #=> ["a", "b", "c"]
I like the solution as it is short, yet it reads very nicely.
Version that keeps the hierarchy of the keys
Works with arrays
Works with nested hashes
keys_only.rb
# one-liner
def keys_only(h); h.map { |k, v| v = v.first if v.is_a?(Array); v.is_a?(Hash) ? [k, keys_only(v)] : k }; end
# nicer
def keys_only(h)
h.map do |k, v|
v = v.first if v.is_a?(Array);
if v.is_a?(Hash)
[k, keys_only(v)]
else
k
end
end
end
hash = { a: 1, b: { c: { d: 3 } }, e: [{ f: 3 }, { f: 5 }] }
keys_only(hash)
# => [:a, [:b, [[:c, [:d]]]], [:e, [:f]]]
P.S.: Yes, it looks like a lexer :D
Bonus: Print the keys in a nice nested list
# one-liner
def print_keys(a, n = 0); a.each { |el| el.is_a?(Array) ? el[1] && el[1].class == Array ? print_keys(el, n) : print_keys(el, n + 1) : (puts " " * n + "- #{el}") }; nil; end
# nicer
def print_keys(a, n = 0)
a.each do |el|
if el.is_a?(Array)
if el[1] && el[1].class == Array
print_keys(el, n)
else
print_keys(el, n + 1)
end
else
puts " " * n + "- #{el}"
end
end
nil
end
> print_keys(keys_only(hash))
- a
- b
- c
- d
- e
- f
def get_all_keys(hash)
hash.map do |k, v|
Hash === v ? [k, get_all_keys(v)] : [k]
end.flatten
end
Please take a look of following code:
hash = {"a" => 1, "b" => {"c" => 3}}
keys = hash.keys + hash.select{|_,value|value.is_a?(Hash)}
.map{|_,value| value.keys}.flatten
p keys
result:
["a", "b", "c"]
New solution, considering #Bala's comments.
class Hash
def recursive_keys
if any?{|_,value| value.is_a?(Hash)}
keys + select{|_,value|value.is_a?(Hash)}
.map{|_,value| value.recursive_keys}.flatten
else
keys
end
end
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}, "e" => {"f" => 3}}
p hash.recursive_keys
result:
["a", "b", "e", "c", "d", "f"]
Also deal with nested arrays that include hashes
def all_keys(items)
case items
when Hash then items.keys + items.values.flat_map { |v| all_keys(v) }
when Array then items.flat_map { |i| all_keys(i) }
else []
end
end
class Hash
def get_all_keys
[].tap do |result|
result << keys
values.select { |v| v.respond_to?(:get_all_keys) }.each do |value|
result << value.get_all_keys
end
end.flatten
end
end
hash = {"a" => 1, "b" => {"c" => 3}}
puts hash.get_all_keys.inspect # => ["a", "b", "c"]
Here is another approach :
def get_all_keys(h)
h.each_with_object([]){|(k,v),a| v.is_a?(Hash) ? a.push(k,*get_all_keys(v)) : a << k }
end
hash = {"a" => 1, "b" => {"c" => {"d" => 3}}}
p get_all_keys(hash)
# >> ["a", "b", "c", "d"]
I'm sure there is a more elegant solution, but this option works:
blah = {"a" => 1, "b" => {"c" => 3}}
results = []
blah.each do |k,v|
if v.is_a? Hash
results << k
v.each_key {|key| results << key}
else
results << k
end
end
puts results
hash.keys is the simplest one I have seen to return an array of the key values in a hash.

Resources