Jill448
Jill448

Reputation: 1793

Create a new hash from an existing array of hashes

I am newbie to ruby . I have an array of hashes input_array

[{
"name"=>"test1", 
"zone_status"=>"valid", 
"certificate_status"=>"valid", 
"users"=>1000,
"name"=>"test2", 
"zone_status"=>"valid", 
"certificate_status"=>"valid", 
"users"=>5000,
"name"=>"test3", 
"zone_status"=>"valid", 
"certificate_status"=>"valid", 
"users"=>3000,
"name"=>"test4", 
"zone_status"=>"valid", 
"certificate_status"=>"valid", 
"users"=>2000}]

and an array

existing_names_array  = ["test1","test2"]

The below line gets me all the names into input_names

input_array.each_with_index {|val, index| input_names << input_array[index]['name'] }

But how can I get the input_names to be a hash with name as key and its respective users as value? Because my final goal is to check the names which are in input_names, but not in existing_names_array and get a count of those names and users

As the names tes1,test2 exists in existing_names_array, I need the count of rest of names and count of their respective users in input_array

Expected output:

output_names = test3, test 4
total_output_names = 2

output_users = 3000,2000
total_output_users = 5000

Upvotes: 0

Views: 98

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110755

I assume input_array is to be as follows.

input_array = [
  { "name"=>"test1", "zone_status"=>"valid", "certificate_status"=>"valid",
    "users"=>1000 },
  { "name"=>"test2", "zone_status"=>"valid", "certificate_status"=>"valid",
    "users"=>5000 },
  { "name"=>"test3", "zone_status"=>"valid", "certificate_status"=>"valid",
    "users"=>3000 },
  { "name"=>"test4", "zone_status"=>"valid", "certificate_status"=>"valid",
    "users"=>2000}
]

We are also given:

names = ["test1", "test2"]

The information of interest can be represented nicely by the following hash (which I will construct):

summary = { "test1"=>1000, "test2"=>5000 }

We might use that to compute the following.

names = summary.keys
  #=> ["test1", "test2"] 
users = summary.values
  #=> [1000, 5000] 
total_users = users.sum
  #=> 6000 

There are many ways to construct the hash summary. I prefer the following.

summary = input_array.each_with_object({}) do |g,h|
  key = g["name"]
  h[key] = g["users"] if names.include?(key)
end
  #=> {"test1"=>1000, "test2"=>5000}

Another way is as follows.

summary = input_array.map { |g| g.values_at("name", "users") }.
  .to_h
  .slice(*names)

See [Hash#values_at(https://ruby-doc.org/core-2.7.0/Hash.html#method-i-values_at), Array#to_h and Hash#slice. The steps are as follows.

arr = input_array.map { |g| g.values_at("name", "users") }
  #=> [["test1", 1000], ["test2", 5000], ["test3", 3000], ["test4", 2000]] 
h = arr.to_h
  #=> {"test1"=>1000, "test2"=>5000, "test3"=>3000, "test4"=>2000} 
summary = h.slice(*names)
  #=> {"test1"=>1000, "test2"=>5000}

The splat operator converts h.slice(*names), which is h.slice(*["test1", "test2"]), to h.slice("test1", "test2"). That's because Hash#slice was defined to accept a variable number of keys as arguments, rather than an array of keys as the argument.

Upvotes: 2

max pleaner
max pleaner

Reputation: 26788

If you use ActiveSupport's Enumerable#index_by with Ruby core's Hash#transform_values it's pretty easy, if I'm understanding your question correctly:

# add gem 'activesupport' to Gemfile, or use Rails, then ..
require 'active_support/all'

users_and_counts = input_array.
  index_by { |hsh| hsh["name"] }.
  transform_values { |hsh| hsh["users"] }

# => { "test1" => 1000, "test2" => 5000, ... }

You can do this with Enumerable#reduce (or Enumerable#each_with_object) as well:

users_and_counts = input_array.reduce({}) do |memo, hsh|
  memo[hsh["name"]] = hsh["users"]
  memo
end

Or, the simplest way, with good old each:

users_and_counts = {}
input_array.each do |hsh|
  users_and_counts[hsh["name"]] = hsh["users"]
end

in response to comment

In general, there are a few ways to check whether an element is found in an array:

array = ["foo", "bar"]

# 1. The simple standard way
array.include?("foo") # => true

# 2. More efficient way
require 'set'
set = Set.new(array)
set.member?("foo") # => true

So with this knowledge we can break up our task into a few steps:

  1. Make a new hash which is a copy of the one we built above, but without the key-vals corresponding to users in the existing_names_array (see Hash#select and Hash#reject):

     require 'set'
     existing_names_set = Set.new(existing_names_array)
     new_users_and_counts = users_and_counts.reject do |name, count|
       existing_names_set.member?(name)
     end
     # => { "test3" => 3000, "test4" => 2000 }
    
  2. Use Hash#keys to get the list of user names:

     new_user_names = new_users_and_counts.keys
     # => ["test3", "test4"]
    
  3. Use Hash#values to get the list of counts:

    new_user_counts = new_users_and_counts.values
    # => [3000, 2000]
    

Upvotes: 2

Related Questions