Home > Software engineering >  On Array of Hashes need to count values while creating other key pairs same time in ruby
On Array of Hashes need to count values while creating other key pairs same time in ruby

Time:09-21

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}}
  • Related