How to merge Ruby hashes - ruby

How can I merge these two hashes:
{:car => {:color => "red"}}
{:car => {:speed => "100mph"}}
To get:
{:car => {:color => "red", :speed => "100mph"}}

There is a Hash#merge method:
ruby-1.9.2 > a = {:car => {:color => "red"}}
=> {:car=>{:color=>"red"}}
ruby-1.9.2 > b = {:car => {:speed => "100mph"}}
=> {:car=>{:speed=>"100mph"}}
ruby-1.9.2 > a.merge(b) {|key, a_val, b_val| a_val.merge b_val }
=> {:car=>{:color=>"red", :speed=>"100mph"}}
You can create a recursive method if you need to merge nested hashes:
def merge_recursively(a, b)
a.merge(b) {|key, a_item, b_item| merge_recursively(a_item, b_item) }
end
ruby-1.9.2 > merge_recursively(a,b)
=> {:car=>{:color=>"red", :speed=>"100mph"}}

Hash#deep_merge
Rails 3.0+
a = {:car => {:color => "red"}}
b = {:car => {:speed => "100mph"}}
a.deep_merge(b)
=> {:car=>{:color=>"red", :speed=>"100mph"}}
Source: https://speakerdeck.com/u/jeg2/p/10-things-you-didnt-know-rails-could-do
Slide 24
Also,
http://apidock.com/rails/v3.2.13/Hash/deep_merge

You can use the merge method defined in the ruby library. https://ruby-doc.org/core-2.2.0/Hash.html#method-i-merge
Example
h1={"a"=>1,"b"=>2}
h2={"b"=>3,"c"=>3}
h1.merge!(h2)
It will give you output like this {"a"=>1,"b"=>3,"c"=>3}
Merge method does not allow duplicate key, so key b will be overwritten from 2 to 3.
To overcome the above problem, you can hack merge method like this.
h1.merge(h2){|k,v1,v2|[v1,v2]}
The above code snippet will be give you output
{"a"=>1,"b"=>[2,3],"c"=>3}

h1 = {:car => {:color => "red"}}
h2 = {:car => {:speed => "100mph"}}
h3 = h1[:car].merge(h2[:car])
h4 = {:car => h3}

Related

array x array matrix in ruby

I'd like to convert from
{'key1' => (1..10) ,
'key2' => (11..20) ,
'key3' => (21..30)}
to
[{'key1' => 1, 'key2' => 11, 'key3' => 21},
{'key1' => 1, 'key2' => 11, 'key3' => 22},...
.
.
{'key1' => 10, 'key2' => 20, 'key3' => 30}]
How to solve it?
Here it is :
hsh = {'key1' => (1..10) ,'key2' => (11..20) , 'key3' => (21..30)}
keys = hsh.keys
hsh['key1'].to_a.product(hsh['key2'].to_a,hsh['key3'].to_a).map{|a|Hash[keys.zip(a)]}
# => [{'key1' => 1, 'key2' => 11, 'key3' => 21},
# {'key1' => 1, 'key2' => 11, 'key3' => 22},...
# .
# .
# {'key1' => 10, 'key2' => 20, 'key3' => 30}]
You could also write the above as below,when you have more number of keys:
hsh = {'key1' => (1..10) ,'key2' => (11..20) , 'key3' => (21..30)}
keys = hsh.keys
array = hsh.values_at(*keys[1..-1]).map(&:to_a)
hsh['key1'].to_a.product(*array).map{|a|Hash[keys.zip(a)]}
So many ways... A kiss answer (edited to extend to any number of keys):
s = {'key1' => (1..10), 'key2' => (11..20), 'key3' => (21..30)}
r = []
s.each {|k,v| a = []; (v.to_a).each {|i| a << {k=>i}}; r << a}
result = r.shift
r.each {|e| result = result.product(e).map(&:flatten)}
result
h = {
'key1' => (1..10),
'key2' => (11..20),
'key3' => (21..30)
}
h.map { |k,v| [k].product(v.to_a) }.transpose.map { |e| Hash[e] }
#=> [{"key1"=>1, "key2"=>11, "key3"=>21},
# {"key1"=>2, "key2"=>12, "key3"=>22},
# {"key1"=>3, "key2"=>13, "key3"=>23},
# {"key1"=>4, "key2"=>14, "key3"=>24},
# {"key1"=>5, "key2"=>15, "key3"=>25},
# {"key1"=>6, "key2"=>16, "key3"=>26},
# {"key1"=>7, "key2"=>17, "key3"=>27},
# {"key1"=>8, "key2"=>18, "key3"=>28},
# {"key1"=>9, "key2"=>19, "key3"=>29},
# {"key1"=>10, "key2"=>20, "key3"=>30}]
h = {'key1' => (1..10) ,
'key2' => (11..20) ,
'key3' => (21..30)}
arrays = h.values.map(&:to_a).transpose
p arrays.map{|ar| Hash[h.keys.zip(ar)] }
#=> [{"key1"=>1, "key2"=>11, "key3"=>21}, {"key1"=>2, "key2"=>12, "key3"=>22},...
h = {'key1' => (1..10), 'key2' => (11..20), 'key3' => (21..30)}
Edit 1: made some changes, principally the use of inject({}):
f,*r = h.map {|k,v| [k].product(v.to_a)}
f.zip(*r).map {|e| e.inject({}) {|h,a| h[a.first] = a.last; h}}
Edit 2: After seeing the use of Hash[] in #Phrogz's answer to another question:
f,*r = h.map {|k,v| [k].product(v.to_a)}
f.zip(*r).map {|e| Hash[*e.flatten]}
Lazier way of doing the same:
h = {
'key1' => (1..10),
'key2' => (11..20),
'key3' => (21..30)
}
result = ( 0...h.values.map( &:to_a ).map( &:size ).max ).map do |i|
Hash.new { |hsh, k| hsh[k] = h[k].to_a[i] }
end
result[1]['key3'] #=> 22

Ruby loops. Hash into string

I am working with Ruby. I need to grab each key/value and put it into a string.
So far I have:
values = ['first' => '1', 'second' => '2']
#thelink = values.collect do | key, value |
"#{key}=#{value}&"
end
When I print #thelink I see:
first1second2=&
But Really what I want is
first=1&second=2
Could anybody help/explain please?
There is something subtle you are missing here {} vs [].
See the below taken from IRB tests:
irb(main):002:0> {'first' => 1, 'second' => 2}
=> {"second"=>2, "first"=>1}
irb(main):003:0> ['first' => 1, 'second' => 2]
=> [{"second"=>2, "first"=>1}]
irb(main):004:0> {'first' => 1, 'second' => 2}.class
=> Hash
irb(main):005:0> ['first' => 1, 'second' => 2].class
=> Array
Similar to this:
irb(main):006:0> {'first' => 1, 'second' => 2}.collect { |key,value| puts "#{key}:#{value}" }
second:2
first:1
=> [nil, nil]
irb(main):007:0> ['first' => 1, 'second' => 2].collect { |key,value| puts "#{key}:#{value}" }
second2first1:
=> [nil]
The array has a single element (a hash) that, as a string, is everything concatenated. This is the important thing to note here.
On the other hand, the hash iterates by handing you the key/value pairs that you are expecting.
Hope that helps.
I think your code has a typo (a hash is delimited by {} not by []). Try this
values = {'first' => '1', 'second' => '2'}
r = values.map{|k,v| "#{k}=#{v}"}.join('&')
puts r
#shows: first=1&second=2

merge some complex hashes in ruby

I'd like to merge the following hashes together.
h1 = {"201201" => {:received => 2}, "201202" => {:received => 4 }}
h2 = {"201201" => {:closed => 1}, "201202" => {:closed => 1 }}
particularly, my expected result is:
h1 = {"201201" => {:received => 2, :closed => 1}, "201202" => {:received => 4, :closed => 1 }}
I have tried every way as:
h = h1.merge(h2){|key, first, second| {first , second} }
unfortunately, neither seemed to work out fine for me.
any advice would be really appreciated.
This should work for you:
h = h1.merge(h2){|key, first, second| first.merge(second)}

Construct a hash with other hashes

I want to join 2 or more hashes like this.
h1 = { :es => { :hello => "You" } }
h2 = { :es => { :bye => "Man" } }
How can I get this?
h1 + h2 = { :es => { :hello => "you", :bye => "Man" } }
Thanks.
irb(main):001:0> h1 = {:es => {:hello => "You"}}
=> {:es=>{:hello=>"You"}}
irb(main):002:0> h2 = {:es => {:bye => "Man"}}
=> {:es=>{:bye=>"Man"}}
irb(main):003:0> h1.each_key {|x| h1[x].merge! h2[x]}
=> {:es=>{:bye=>"Man", :hello=>"You"}}
What you want is the deep_merge method. Does exactly what you want.
ruby-1.9.2-p136 :001 > {:es => {:hello => "You" } }.deep_merge({:es => {:bye => "Man"}})
=> {:es=>{:hello=>"You", :bye=>"Man"}}
http://apidock.com/rails/ActiveSupport/CoreExtensions/Hash/DeepMerge/deep_merge
Similar to activesupport's deep_merge, but with a functional approach. Works recursively:
class Hash
def inner_merge(other_hash)
other_hash.inject(self) do |acc, (key, value)|
if (acc_value = acc[key]) && acc_value.is_a?(Hash) && value.is_a?(Hash)
acc.merge(key => acc_value.inner_merge(value))
else
acc.merge(key => value)
end
end
end
end
h1.inner_merge(h2) #=> {:es=>{:hello=>"You", :bye=>"Man"}}
If you don't use ActiveSupport, this Proc will perform a deep merge. 1.8.7 & 1.9.2 compatible.
dm = lambda {|l,r| l.merge(r) {|k,ov,nv| l[k] = ov.is_a?(Hash) ? dm[ov, nv || {}] : nv} }
dm[h1,h2]
# => {:es=>{:hello=>"You", :bye=>"Man"}}

How to change format of nested hashes

I'm looking for a solution how to write the format function which will take a string or nested hash as an argument and return the flatten version of it with the path as a key.
arg = "foo"
format(arg) # => { "hash[keys]" => "foo" }
arg = {:a => "foo", :b => { :c => "bar", :d => "baz" }}
format(arg) # => { "hash[keys][a]" => "foo", "hash[keys][b][c]" => "bar", "hash[keys][b][d]" => "baz" }
def hash_flatten h
h.inject({}) do |a,(k,v)|
if v.is_a?(Hash)
hash_flatten(v).each do |sk, sv|
a[[k]+sk] = sv
end
else
k = k ? [k] : []
a[k] = v
end
a
end
end
def format h
if h.is_a?(Hash)
a = hash_flatten(h).map do |k,v|
key = k.map{|e| "[#{e}]"}.join
"\"event[actor]#{key}\" => \"#{v}\""
end.join(', ')
else
format({nil => h})
end
end
arg = "sth"
puts format(arg)
# => "event[actor]" => "sth"
arg = {:a => "sth", :b => { :c => "sth else", :d => "trololo" }}
puts format(arg)
# => "event[actor][a]" => "sth", "event[actor][b][c]" => "sth else", "event[actor][b][d]" => "trololo"

Resources