简体   繁体   中英

How to merge two arrays of hashes by the same pair of key and value ruby

I'm new in ruby. I have two hashes:

f = { "server"=>[{ "hostname"=>"a1", "ip"=>"10" }, {"hostname"=>"b1", "ip"=>"10.1" }] }
g = { "admin" =>[{ "name"=>"adam", "mail"=>"any", "hostname"=>"a1" },
                 { "name"=>"mike", "mail"=>"id", "hostname"=>"b1"}]}

and I want to get another hash like this:

{ "data" => [{"hostname"=>"a1", "ip"=>"10", "name" =>"adam", "mail"=>"any"},
             {"hostname"=>"b1", "ip"=>"10.1", "name" =>"mike", "mail"=>"id"}]}

The pairs "hostname"=>"something" always matches in hashes of both arrays. I have tried something like this:

data = server.merge(admin)

but it isn't so easy and as you expect it doesn't work. Could you help me merge these hashes and explain for the future how you did it?

A quick way that i can think of right now will look like:

servers = { "server" => [{"hostname"=>"a1", "ip"=>"10"}, {"hostname"=>"b1", "ip"=>"10.1"}]}
admins = { "data" => [{"hostname"=>"a1", "ip"=>"10", "name" =>"adam", "mail"=>"any"}, {"hostname"=>"b1", "ip"=>"10.1", "name" =>"mike", "mail"=>"id"}]}
# FYI: you can just use arrays for representing the above data, you don't necessarily need a hash.
list_of_entries = (servers.values + admins.values).flatten
grouped_by_hostname_entries = list_of_entries.group_by { |h| h['hostname'] }
grouped_by_hostname_entries.map { |_, values| values.inject({}, :merge) }
#=> [{"hostname"=>"a1", "ip"=>"10", "name"=>"adam", "mail"=>"any"}, {"hostname"=>"b1", "ip"=>"10.1", "name"=>"mike", "mail"=>"id"}]

Code and example

ff = f["server"].each_with_object({}) { |g,h| h[g["hostname"]] = g }
  #=> {"a1"=>{"hostname"=>"a1", "ip"=>"10"}, "b1"=>{"hostname"=>"b1", "ip"=>"10.1"}}

{ "data"=>g["admin"].map { |h| h.merge(ff[h["hostname"]]) } } 
  #=> {"data"=>[{"name"=>"adam", "mail"=>"any", "hostname"=>"a1", "ip"=>"10"},
  #             {"name"=>"mike", "mail"=>"id", "hostname"=>"b1", "ip"=>"10.1"}]} 

Explanation

We want to produce a hash

{ "data"=>arr }

where

arr #=> [{ "name"=>"adam", "mail"=>"any", "hostname"=>"a1", "ip"=>"10" },
    #    { "name"=>"mike", "mail"=>"id", "hostname"=>"b1", "ip"=>"10.1" }]

so we need only compute arr .

First, we create the hash

ff = f["server"].each_with_object({}) { |g,h| h[g["hostname"]] = g }
  #=> {"a1"=>{"hostname"=>"a1", "ip"=>"10"}, "b1"=>{"hostname"=>"b1", "ip"=>"10.1"}}

We have

enum = f["server"].each_with_object({})
  #=> #<Enumerator: [{"hostname"=>"a1", "ip"=>"10"},
  #                  {"hostname"=>"b1", "ip"=>"10.1"}]:each_with_object({})>

We can see the elements that will be generated by this enumerator (and passed to its block) by converting it to an array:

enum.to_a
  #=> [[{"hostname"=>"a1", "ip"=>"10"}, {}],
  #    [{"hostname"=>"b1", "ip"=>"10.1"}, {}]] 

Note

enum.each { |g,h| h[g["hostname"]] = g }
  #=> {"a1"=>{"hostname"=>"a1", "ip"=>"10"},
  #    "b1"=>{"hostname"=>"b1", "ip"=>"10.1"}} 

each passes the first element of enum and assigns the block variables using parallel assignement (also call multiple assignment ):

g,h = enum.next
  #=> [{"hostname"=>"a1", "ip"=>"10"}, {}] 
g #=> {"hostname"=>"a1", "ip"=>"10"} 
h #=> {} 

We may now perform the block calculation:

h[g["hostname"]] = g
  #=> h["a1"] = {"hostname"=>"a1", "ip"=>"10"} 
  #=> {"hostname"=>"a1", "ip"=>"10"}

The return value is the new value of the block variable h . The second element of enum is then passed to the block and the block calculation is performed:

g,h = enum.next
  #=> [{"hostname"=>"b1", "ip"=>"10.1"}, {"a1"=>{"hostname"=>"a1", "ip"=>"10"}}] 
g #=> {"hostname"=>"b1", "ip"=>"10.1"} 
h #=> {"a1"=>{"hostname"=>"a1", "ip"=>"10"}} 

Notice that the hash h has been updated.

h[g["hostname"]] = g
  #=> {"hostname"=>"b1", "ip"=>"10.1"} 

So now

h #=> {"a1"=>{"hostname"=>"a1", "ip"=>"10"},
  #    "b1"=>{"hostname"=>"b1", "ip"=>"10.1"}} 

and

ff #=> {"a1"=>{"hostname"=>"a1", "ip"=>"10"}, "b1"=>{"hostname"=>"b1", "ip"=>"10.1"}}

Now we can compute arr :

g["admin"].map { |h| h.merge(ff[h["hostname"]]) }

The first element of g["admin"] is passed to the block and assigned to the block variable:

h = g["admin"][0]
  #=> {"name"=>"adam", "mail"=>"any", "hostname"=>"a1"}

and the block calculation is performed:

h.merge(ff[h["hostname"]])
  #=> h.merge(ff["a1"])
  #=> h.merge({"hostname"=>"a1", "ip"=>"10"})
  #=> {"name"=>"adam", "mail"=>"any", "hostname"=>"a1", "ip"=>"10"} 

Then

h = g["admin"][1]
  #=> {"name"=>"mike", "mail"=>"id", "hostname"=>"b1"} 

h.merge(ff[h["hostname"]])
  #=> h.merge(ff["b1"])
  #=> h.merge({"hostname"=>"a2", "ip"=>"10"})
  #=> {"name"=>"mike", "mail"=>"id", "hostname"=>"a2", "ip"=>"10"}

Therefore,

arr
  #=> [{"name"=>"adam", "mail"=>"any", "hostname"=>"a1", "ip"=>"10"},
  #=>  {"name"=>"mike", "mail"=>"id", "hostname"=>"b1", "ip"=>"10.1"}] 

is returned by the block and we are finished.

As another variant you can try this

h1 = { "server" => [{"hostname"=>"a1", "ip"=>"10"}, {"hostname"=>"b1", "ip"=>"10.1"}]}
h2 = { "admin"  => [{"name" =>"adam", "mail"=>"any", "hostname"=>"a1"}, {"name" =>"mike", "mail"=>"id", "hostname"=>"b1"}]}
h1['server'].zip(h2['admin']).map { |ar| ar.first.merge(ar.last) }

#=> [{"hostname"=>"a1", "ip"=>"10", "name"=>"adam", "mail"=>"any"}, {"hostname"=>"b1", "ip"=>"10.1", "name"=>"mike", "mail"=>"id"}]

zip let us iterate through two or more arrays at the same time.
We use map to return result.

In map block ar would be equal

  • [{"hostname"=>"a1", "ip"=>"10"}, {"name"=>"adam", "mail"=>"any", "hostname"=>"a1"}]
  • [{"hostname"=>"b1", "ip"=>"10.1"}, {"name"=>"mike", "mail"=>"id", "hostname"=>"b1"}]

So ar.first would be {"hostname"=>"a1", "ip"=>"10"} and the ar.last would be {"name"=>"adam", "mail"=>"any", "hostname"=>"a1"}

Finally we use merge to combine two hashes.
Hope this will help.

f = { "server"=>[{ "hostname"=>"a1", "ip"=>"10" }, 
{"hostname"=>"b1",   "ip"=>"10.1" }] }

g = { "admin" =>[{ "name"=>"adam", "mail"=>"any", "hostname"=>"a1" },
{ "name"=>"mike", "mail"=>"id", "hostname"=>"b1"}]}

# manual way
host_admin_merge = []
host_admin_merge << f["server"].first.merge(g["admin"].first)
host_admin_merge << f["server"].last.merge(g["admin"].last)

# a bit more automated, iterate, test key's value, append to new array
host_admin_merge = []
f["server"].each do |host|
  g["admin"].each do |admin|
    if admin[:hostname] == host[:hostname]
      host_admin_merge << host.merge(admin)
    end
  end
end

# assign the array to a hash with "data" as the key
host_admin_hash = {}
host_admin_hash["data"] = host_admin_merge
p host_admin_hash

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM