I need help counting ocurrences inside the array of hashes "aoh" and producing and expected output "summary".
This is the original data "aoh":
aoh = [{:interface=>"1A",
:host=>"host_1",
:status=>"online"},
{:interface=>"1A",
:host=>"host_2",
:status=>"online"},
{:interface=>"1A",
:host=>"host_3",
:status=>"offline"},
{:interface=>"2A",
:host=>"host_4",
:status=>"offline"},
{:interface=>"2A",
:host=>"host_5",
:status=>"offline"},
{:interface=>"2A",
:host=>"host_6",
:status=>"online"}
]
And this is the Expected Ouput
summary = [{:interface=>"1A", :online_hosts=> 2, :offline_hosts=> 1},
{:interface=>"2A", :online_hosts=> 1, :offline_hosts=> 2}]
Note: I was trying this code...
summary = Hash.new { |h, k| h[k] = { online: 0, offline: 0 } }
aoh.each do |item|
summary[item[:interface]][item[:status] == 'online' ? :online : :offline] = 1
end
summary
=> {"1A"=>{:online=>2, :offline=>1}, "2A"=>{:online=>1, :offline=>2}} # almost, but it is not the expected result
and almost this produce the expected result, but this way interface value was moved outside the hash as a key, and hash key names are not the expected. I do really need keep inside the hash the interface key pair also.
CodePudding user response:
If you try to directly build an array of hashes, you have to scan the array every iteration to find the interface's summary. That's awkward and expensive.
Building a hash of hashes is easier and faster. Do the same thing you're doing, but also store the interface name in the summary. Then take the values of the resulting hash.
# Same thing, but also store the interface name in the value.
summary = Hash.new do |h, k|
h[k] = { interface: k, online: 0, offline: 0 }
end
# Same thing, just easier to read.
aoh.each do |item|
status = item[:status] == 'online' ? :online : :offline
summary[item[:interface]][status] = 1
end
# What you want is the values of the hash.
p summary.values
CodePudding user response:
You could use Enumerable#group_by.
aoh.group_by { |h| h[:interface] }
.map do |k,a|
n = a.count { |h| h[:status] == "online" }
{ interface: k, online_hosts: n, offline_hosts: a.size - n }
end
#=> [{:interface=>"1A", :online_hosts=>2, :offline_hosts=>1},
# {:interface=>"2A", :online_hosts=>1, :offline_hosts=>2}]
Note:
aoh.group_by { |h| h[:interface] }
#=> {"1A"=>[{:interface=>"1A", :host=>"host_1", :status=>"online"},
# {:interface=>"1A", :host=>"host_2", :status=>"online"},
# {:interface=>"1A", :host=>"host_3", :status=>"offline"}],
# "2A"=>[{:interface=>"2A", :host=>"host_4", :status=>"offline"},
# {:interface=>"2A", :host=>"host_5", :status=>"offline"},
# {:interface=>"2A", :host=>"host_6", :status=>"online"}]}
Another way is to use the form of Hash#update (a.k.a. merge!
) that employs a block to determine the value of keys that are present in both hashes being merged. Here that block is do |_,o,n| ... end
. See the doc for the definitions of the three block variables, _
, o
and n
. (_
holds the common key. I've used an underscore to represent that variable to signal to the reader that it is not used in the block calculation, a common convention.)
aoh.each_with_object({}) do |g,h|
h.update(
g[:interface]=>
{ interface: g[:interface],
online_hosts: g[:status] == "online" ? 1 : 0,
offline_hosts: g[:status] == "online" ? 0 : 1
}
) do |_,o,n|
{ interface: o[:interface],
online_hosts: o[:online_hosts] n[:online_hosts],
offline_hosts: o[:offline_hosts] n[:offline_hosts]
}
end
end.values
#=> [{:interface=>"1A", :online_hosts=>2, :offline_hosts=>1},
# {:interface=>"2A", :online_hosts=>1, :offline_hosts=>2}]
Note that the receiver of Hash#values is:
{"1A"=>{:interface=>"1A", :online_hosts=>2, :offline_hosts=>1},
"2A"=>{:interface=>"2A", :online_hosts=>1, :offline_hosts=>2}}