Weston
Weston

Reputation: 169

How to iterate over a nested Ruby hash to add a new key/value pair based on existing key/value data?

  1. I have a nested hash of data:

    munsters = {
      "Herman" => { "age" => 32, "gender" => "male" },
      "Lily" => { "age" => 30, "gender" => "female" },
      "Grandpa" => { "age" => 402, "gender" => "male" },
      "Eddie" => { "age" => 10, "gender" => "male" },
      "Marilyn" => { "age" => 23, "gender" => "female"}
    }
    
  2. I want to loop through the hash for each member and look up their age in the nested hash.

  3. For each member, I want to add a new key/value pair to the existing nested hash called "age_group".

  4. The value of each member's "age_group" will be dependent on their age. For example, if the age is above 65 years I want to have their age_group read "senior", etc.

Problems I'm running into:

I'm confused how I would access just the "age" key_value pair of the nested hash when the first key is different for each member of the family. Meaning, I can't do something like munsters["age"] because that returns nil (assuming because "age" is nested).

If I had a simple (un-nested) hash it's pretty straightforward. Example of a non-nested hash:

ages = { "Herman" => 32, "Lily" => 30, "Grandpa" => 402, "Eddie" => 10 }

I would then likely loop though like this:

age_group = {}

ages.each do |k, v|
  if v >= 65
    puts "#{k}'s age of #{v} makes them a senior!"
    age_group.merge!("age_group": "senior")
  elsif v > 17 && v < 65
    puts "#{k}'s age of #{v} makes them an adult"
    age_group.merge!("age_group": "adult")
  else
    puts "#{k}'s age of #{v} makes them a kid."
    age_group.merge!("age_group": "kid")
  end
end

For the nested hash, I'm able to access the entire nested hash like this:

munsters.each do |k, v|
  v.each do |k2, v2|
    p "k2 is #{k2} and v2 is #{v2}"
  end
end

But that still only returns the entire nested hash to the console instead of just the age:

k2 is age and v2 is 32
k2 is gender and v2 is male

Upvotes: 1

Views: 88

Answers (4)

3limin4t0r
3limin4t0r

Reputation: 21110

The problem with your current attempt is that you try to do something for each key/value-pair of each munster. However you are only interested in the "age" and "age_group". So there is no need to iterate the key/value-pairs of munster. Instead work directly on munster.

Other examples mostly show mutating solution, so let me offer a non-mutating alternative (meaning that munsters doesn't change). I've also removed the if-statement to provide an alternative perspective.

threshholds = { "kid" => 0, "adult" => 18, "senior" => 65 }

age_groups = threshholds.each_key.sort_by(&threshholds)
new_munsters = munsters.transform_values do |munster|
  age_group = age_groups
    .take_while { |age_group| munster["age"] > threshholds[age_group] }
    .last

  munster.merge("age_group" => age_group)
end
#=> {
#   "Herman"=>{"age"=>32, "gender"=>"male", "age_group"=>"adult"},
#   "Lily"=>{"age"=>30, "gender"=>"female", "age_group"=>"adult"},
#   "Grandpa"=>{"age"=>402, "gender"=>"male", "age_group"=>"senior"},
#   "Eddie"=>{"age"=>10, "gender"=>"male", "age_group"=>"kid"},
#   "Marilyn"=>{"age"=>23, "gender"=>"female", "age_group"=>"adult"}
# }

This solution uses transform_values to create a new version of munsters, containing new updated versions of munster (created with merge).

Upvotes: 0

Cary Swoveland
Cary Swoveland

Reputation: 110665

def age_group(age)
  case age
  when 0..17 then "kid"
  when 18..64 then "adult"
  else "senior"
  end
end

munsters.each_value { |h| h["age_group"] = age_group(h["age"]) }
  #=> {"Herman" =>{"age"=>32,  "gender"=>"male",   "age_group"=>"adult"},
  #    "Lily"   =>{"age"=>30,  "gender"=>"female", "age_group"=>"adult"},
  #    "Grandpa"=>{"age"=>402, "gender"=>"male",   "age_group"=>"senior"}, 
  #    "Eddie"  =>{"age"=>10,  "gender"=>"male",   "age_group"=>"kid"}, 
  #    "Marilyn"=>{"age"=>23,  "gender"=>"female", "age_group"=>"adult"}}

This return value is the new value of munsters.

Upvotes: 1

Oleksandr Holubenko
Oleksandr Holubenko

Reputation: 4440

In addition to @MurifoX answer, you can use #transform_values

ages.transform_values do |value|
  if value["age"] >= 65
    value["age_group"] = "senior"
  elsif value["age"] > 17 && value["age"] < 65
    value["age_group"] = "adult"
  else
    value["age_group"] = "kid"
  end
end

Upvotes: 2

MurifoX
MurifoX

Reputation: 15089

The v is a hash too. So you can do it like this:

ages.each do |k, v|
  if v["age"] > 60
    ages[k]["age_group"] = "adult"
  else
    ages[k]["age_group"] = "kid"
  end
end

You add a age_group key with a string value to the k position of your age hash.

Upvotes: 1

Related Questions