Merge two hashes on a particular value - ruby

I am checking whether the hash hash_volumes below has a key whose instance_id matches with a key of hash hash_instance.
hash_volumes = {
:"vol-d16d12b8" => {
:instance_id => "i-4e4ba679",
},
}
hash_instance = {
:"i-4e4ba679" => {
:arch => "x86_64",
},
}
If it does, then I need to merge it to the hash_instance. I find that vol-d16d12b8 matches with the instance i-4e4ba679 and hence I want to merge it with hash_instance so that the final hash_instance will look like below:
hash_instance = {
:"i-4e4ba679" => {
:arch => "x86_64",
:volume => "vol-d16d12b8" # this is new entry to `hash_instance`
},
}
I am not able to merge these two hashes as explained above. I suspect my if statement is wrong. Please take a look at my code below:
hash_volumes.each_key do |x|
hash_instance.each_key do |y|
if hash_volumes[x][:instance_id] == y ## I think this line is the problem
hash_instance[y][:volume] = x
end
end
end
hash_instance
Output:
{
:"i-4e4ba679" => {
:arch => "x86_64"
}
}
The code above gives hash_instance without adding volume to it. I tried as below, but none worked:
if hash_volumes[x][:instance_id] == "#{y}"
# => this if statement gives me syntax error
.....
if hash_volumes[x][:instance_id] =~ /"#{y}"/
# => this if statement does not make any changes to above output.

hash_volumes = {
:"vol-d16d12b8" => {
:instance_id => "i-4e4ba679",
},
}
hash_instance = {
:"i-4e4ba679" => {
:arch => "x86_64",
},
}
hash_volumes.each do |key, val|
id = val[:instance_id] #returns nil if the there is no :instance_id key
if id
id_as_sym = id.to_sym
if hash_instance.has_key? id_as_sym
hash_instance[id_as_sym][:volume] = id
end
end
end
--output:--
{:"i-4e4ba679"=>{:arch=>"x86_64", :volume=>"i-4e4ba679"}}

A simple implementation would be this:
hash_instance.each do |k1, v1|
next unless k = hash_volumes.find{|k2, v2| v2[:instance_id].to_sym == k1}
v1[:volume] = k.first
end

Related

Merge Ruby nested hashes with same keys

I have several hashes in Ruby which have nested hashes inside of them an share very similar structure. They look something like this:
a = {
"year_1": {
"sub_type_a": {
"label1": value1
}
},
"year_2": {
"sub_type_a": {
"label2": value2
}
}
}
b = {
"year_1": {
"sub_type_a": {
"label3": value3
}
},
"year_2": {
"sub_type_a": {
"label4": value4
}
}
}
c = {
"year_1": {
"sub_type_a": {
"label5": value5
}
},
"year_2": {
"sub_type_a": {
"label6": value6
}
}
}
I want to combine them in one single hash which would have the nested data combined where possible without overwriting other values like this:
result = {
"year_1": {
"sub_type_a": {
"label1": value1,
"label3": value3,
"label5": value5
}
},
"year_2": {
"sub_type_a": {
"label2": value2,
"label4": value4,
"label6": value6
}
}
}
There could also be several sub types instead of just one but that's the general idea.
If I use the merge function it just overwrites the label-value data inside the sub_type hashes and I am left with only one record.
Is there a simple way to achieve this? I can write a function that iterates the hashes recursively and figure out inside what to add where but it feels like that there should be a simpler way.
Something similar.
Combine each_with_object, each and merge so you can iterate trough each hash and assign the merged values when they exist to a temporal new one:
[a, b, c].each_with_object({}) do |years_data, hash|
years_data.each do |year, data|
hash[year] = (hash[year] || {}).merge(data) { |_, oldval, newval| oldval.merge(newval) }
end
end
# {
# :year_1 => {
# :sub_type_a => {
# :label1 => :value1,
# :label3 => :value3,
# :label5 => :value5
# }
# },
# :year_2 => {
# :sub_type_a => {
# :label2 => :value2,
# :label4 => :value4,
# :label6 => :value6
# }
# }
# }
If you are using Rails (or ActiveSupport) you might want to look at deep_merge, which handles merging of nested hashes for you
We are given the following.
a = {:year_1=>{:sub_type_a=>{:label1=>"value1"}},
:year_2=>{:sub_type_a=>{:label2=>"value2"}}}
b = {:year_1=>{:sub_type_a=>{:label3=>"value3"}},
:year_2=>{:sub_type_a=>{:label4=>"value4"}}}
c = {:year_1=>{:sub_type_a=>{:label5=>"value5"}},
:year_2=>{:sub_type_a=>{:label6=>"value6"}}}
arr = [a, b, c]
We may construct the desired hash as follows.
arr.each_with_object({}) do |g,h|
g.each do |yr,v|
k,f = v.first
h.update(yr=>{ k=>f }) { |_,o,n| { k=>o[k].merge(n[k]) } }
end
end
#=> {:year_1=>{:sub_type_a=>{:label1=>"value1", :label3=>"value3",
# :label5=>"value5"}},
# :year_2=>{:sub_type_a=>{:label2=>"value2", :label4=>"value4",
# :label6=>"value6"}}}
This uses the form of Hash#update (a.k.a merge!) that employs a block to determine the values of keys that are present in both hashes being merged. See the link for an explanation of that block's three block variables. I've used an underscore (a valid local variable) for the first block variable, the common key, to signal to the reader that it is not used in the block calculation. That is a common convention.
For anyone interested in the gory detail of the calculations (the one sure-fire way to understand what's going on) I will execute the code with some puts statements added.
arr.each_with_object({}) do |g,h|
puts "g=#{g}"
puts "h=#{h}"
g.each do |yr,v|
puts " yr=#{yr}"
puts " v=#{v}"
k,f = v.first
puts " k=#{k}"
puts " f=#{f}"
puts " yr=>{ k=>f } = #{yr}=>#{v} = #{{ yr=>v }}"
h.update(yr=>{ k=>f }) do |_,o,n|
puts " _=#{_}"
puts " o=#{o}"
puts " n=#{n}"
puts " { k=>o[k].merge(n[k]) }"
puts " => { #{k}=>#{o[k]}.merge(#{n[k]}) }"
{ k=>o[k].merge(n[k]) }.tap { |e| puts " => #{e}" }
end
end
end
The following is displayed.
g={:year_1=>{:sub_type_a=>{:label1=>"value1"}},
:year_2=>{:sub_type_a=>{:label2=>"value2"}}}
h={}
yr=year_1
v={:sub_type_a=>{:label1=>"value1"}}
k=sub_type_a
f={:label1=>"value1"}
yr=>{ k=>f } = year_1=>{:sub_type_a=>{:label1=>"value1"}} =
{:year_1=>{:sub_type_a=>{:label1=>"value1"}}}
yr=year_2
v={:sub_type_a=>{:label2=>"value2"}}
k=sub_type_a
f={:label2=>"value2"}
yr=>{ k=>f } = year_2=>{:sub_type_a=>{:label2=>"value2"}} =
{:year_2=>{:sub_type_a=>{:label2=>"value2"}}}
g={:year_1=>{:sub_type_a=>{:label3=>"value3"}},
:year_2=>{:sub_type_a=>{:label4=>"value4"}}}
h={:year_1=>{:sub_type_a=>{:label1=>"value1"}},
:year_2=>{:sub_type_a=>{:label2=>"value2"}}}
yr=year_1
v={:sub_type_a=>{:label3=>"value3"}}
k=sub_type_a
f={:label3=>"value3"}
yr=>{ k=>f } = year_1=>{:sub_type_a=>{:label3=>"value3"}} =
{:year_1=>{:sub_type_a=>{:label3=>"value3"}}}
_=year_1
o={:sub_type_a=>{:label1=>"value1"}}
n={:sub_type_a=>{:label3=>"value3"}}
{ k=>o[k].merge(n[k]) }
=> { sub_type_a=>{:label1=>"value1"}.
merge({:label3=>"value3"}) }
=> {:sub_type_a=>{:label1=>"value1", :label3=>"value3"}}
yr=year_2
v={:sub_type_a=>{:label4=>"value4"}}
k=sub_type_a
f={:label4=>"value4"}
yr=>{ k=>f } = year_2=>{:sub_type_a=>{:label4=>"value4"}} =
{:year_2=>{:sub_type_a=>{:label4=>"value4"}}}
_=year_2
o={:sub_type_a=>{:label2=>"value2"}}
n={:sub_type_a=>{:label4=>"value4"}}
{ k=>o[k].merge(n[k]) }
=> { sub_type_a=>{:label2=>"value2"}.
merge({:label4=>"value4"}) }
=> {:sub_type_a=>{:label2=>"value2", :label4=>"value4"}}
g={:year_1=>{:sub_type_a=>{:label5=>"value5"}},
:year_2=>{:sub_type_a=>{:label6=>"value6"}}}
h={:year_1=>{:sub_type_a=>{:label1=>"value1", :label3=>"value3"}},
:year_2=>{:sub_type_a=>{:label2=>"value2", :label4=>"value4"}}}
yr=year_1
v={:sub_type_a=>{:label5=>"value5"}}
k=sub_type_a
f={:label5=>"value5"}
yr=>{ k=>f } = year_1=>{:sub_type_a=>{:label5=>"value5"}} =
{:year_1=>{:sub_type_a=>{:label5=>"value5"}}}
_=year_1
o={:sub_type_a=>{:label1=>"value1", :label3=>"value3"}}
n={:sub_type_a=>{:label5=>"value5"}}
{ k=>o[k].merge(n[k]) }
=> { sub_type_a=>{:label1=>"value1", :label3=>"value3"}.
merge({:label5=>"value5"}) }
=> {:sub_type_a=>{:label1=>"value1", :label3=>"value3",
:label5=>"value5"}}
yr=year_2
v={:sub_type_a=>{:label6=>"value6"}}
k=sub_type_a
f={:label6=>"value6"}
yr=>{ k=>f } = year_2=>{:sub_type_a=>{:label6=>"value6"}} =
{:year_2=>{:sub_type_a=>{:label6=>"value6"}}}
_=year_2
o={:sub_type_a=>{:label2=>"value2", :label4=>"value4"}}
n={:sub_type_a=>{:label6=>"value6"}}
{ k=>o[k].merge(n[k]) }
=> { sub_type_a=>{:label2=>"value2", :label4=>"value4"}.
merge({:label6=>"value6"}) }
=> {:sub_type_a=>{:label2=>"value2", :label4=>"value4",
:label6=>"value6"}}
=> {:year_1=>{:sub_type_a=>{:label1=>"value1", :label3=>"value3",
:label5=>"value5"}},
:year_2=>{:sub_type_a=>{:label2=>"value2", :label4=>"value4",
:label6=>"value6"}}}
Hash#merge takes an optional conflict resolution block, which will be called any time a key is present in both the subject and the parameter.
You can use this to e.g. recursively merge your hashes.

Building a data structure in Ruby

Am am trying to build a data structure by looping over an array.
This is what I have
a = ['a1', 'a2']
def func(name)
{name =>
{'key1'=> 'value2',
'key2'=> 'value2'
}
}
end
content = {'root'=>
a.each do | v |
func(v)
end
}
Which gives me
{"root"=>["a1","a2"]}
I am after this
{
"r"=> {
"a1"=> {
"key1"=> "value2",
"key2"=> "value2"
},
"a2"=> {
"key1"=> "value2",
"key2"=> "value2"
}
}
}
Can someone show me where I am going wrong?
You're using each, which only return the array itself. Instead of that, you need to use map:
content = { 'root' =>
a.map do | v |
func(v)
end
}
Or in a better syntax, since the block is one line:
content = {
'root' => a.map { |v| func(v) }
}
It still needs some refactoring but you will get the idea. If you use map it will return array and resulted "r" will be array and not hash.
{"r": a.each_with_object(Hash.new(0))
{ |h1, h2| h2[h1]={"key1"=>"value2", "key2"=>"value2"} }
}
it will return exactly
{:r=>{
"a1"=>{"key1"=>"value2", "key2"=>"value2"},
"a2"=>{"key1"=>"value2","key2"=>"value2"}
}
}

Use user and pass based on banner in telnet class of ruby

I want to write a script that does a login with username and password based on the output of the banner from a connection made by Net::Telnet in ruby:
code to login and execute a few commands (commands are in file "commands"):
USER = "myuser"
PASS = "mypass"
USER2 = "my2ndUser"
PASS2 = "my2ndPass"
ENABLEPASS = "myenablepass"
ENABLEPASS2 = "my2ndEnablePass"
tn = Net::Telnet::new("Host" => line,
"Timeout" => 10,
"Waittime" => 0.1,
"Prompt" => /[#>:]/n) { |resp| print "==> "+resp}
tn.cmd(USER) { |c| print c }
tn.cmd(PASS) { |c| print c }
tn.cmd("terminal length 0") { |c| print c }
tn.cmd("en") { |c| print c }
tn.cmd(ENABLEPASS) { |c| print c }
tn.cmd("\n") { |c| print c }
File.open('commands').each do |l|
tn.cmd(l) { |c| print c }
end
tn.cmd("\n") { |c| print c }
tn.cmd("\n") { |c| print c }
tn.close
Desired: same script but uses the 2nd parameters when it says "my2ndrouter" in the banner/MOTD of the router that is outputted when a login attempt is done (just before you have to insert your user ans pass)
If I got the scenario right, you can use waitfor to syphon in the data until you get the prompt to receive the user name. Then process the data in waitfor block to detect which of the two sets of parameters to use.
routers = [
{ "user" => "myuser", "pass" => "mypass", "enablepass" => "myendablepass" },
{ "user" => "my2ndUser", "pass" => "my2ndPass", "enablepass" => "my2ndEndablePass" }
]
tn = Net::Telnet::new("Host" => line,
"Timeout" => 10,
"Waittime" => 0.1,
"Prompt" => /[#>:]/n) { |resp| print "==> "+resp}
my_router = routers[0]
# Replace /login:/ with whatever regexp that matches your login prompt, e.g. your catch-all /[#>:]/n
tn.waitfor(/login:/) { |banner| my_router = routers[1] if banner.match("my2ndrouter") }
tn.cmd(my_router['user']) { |c| print c }
tn.cmd(my_router['pass']) { |c| print c }
tn.cmd("terminal length 0") { |c| print c }
tn.cmd("en") { |c| print c }
tn.cmd(my_router['enablepass']) { |c| print c }
tn.cmd("\n") { |c| print c }
File.open('commands').each do |l|
tn.cmd(l) { |c| print c }
end
tn.cmd("\n") { |c| print c }
tn.cmd("\n") { |c| print c }
tn.close

Setting values in array iteratively in ruby

Is there a simpler way to do this in ruby?
ops_schema_name = "ops"
tables.each do |table|
table.schema_name = ops_schema_name
end
When reading properties, is it as simple as tables.collect(&:schema_name)?
I am assuming there is a shortcut for setters.
You could do
schema_name_updater = -> table { table.schema_name = 'ops' }
tables.each(&schema_name_updater)
Based on the good Jörg's answer, I came up with this generic solution, for any kind of objects and any number of attributes:
attrs_setter = -> obj, attrs { attrs.each { |k, v| obj.send("#{k}=", v) } }
$> tables.each { |table| attrs_setter.call(table, { :name => 'ops' }) }
$> Obj = Struct.new(:a, :b)
# => Obj
$> objs = Array.new(2) { |i| i = Obj.new('hi', 'there') }
# => [#<struct Obj a="hi", b="there">, #<struct Obj a="hi", b="there">]
$> objs.each { |obj| attrs_setter.call(obj, { a: 'good', b: 'bye' } ) }
# => [#<struct Obj a="good", b="bye">, #<struct Obj a="good", b="bye">]

How do I add a value to a nested hash?

I have a nested hash:
hash = {
"a" => "a",
"b" => {
"c" => "c",
"d" => {
"e" => "e"
}
}
}
and I have a hash:
new_value = {
"b.d.e" => "new value"
}
I need some sort of "magical" function that replaces the value of the hash at hash["b"]["d"]["e"], like:
magical_function(hash, new_value)
#=> hash = {
"a" => "a",
"b" => {
"c" => "c",
"d" => {
"e" => "new value"
}
}
}
I have no idea how. Can someone help please?
It's not magical if it's implemented in a straight-forward manner:
merge_hash.each do |key, value|
parts = key.split('.')
leaf = parts.pop
target = parts.inject(hash) do |h, k|
h[k] ||= { }
end
target[leaf] = value
end
Here's another solution:
class Hash
def replace_value(*keys, value)
current = self
current = current[keys.shift] while keys.size > 1
current[keys.last] = value
end
end
Called by invoking hash.replace_value("b","d","e", "new_value").

Resources