Sign up ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Given two arrays (for instance a list of people and a list of subjects), I need to initialise a Hash with all zeros for all possible combinations of these two arrays. I can manage to do that with the code on the bottom, but it seems like there should be a less convoluted, more elegant way to do this.

people = %w(tom mary rob)
subjects = %w(math english)

Hash[people.map { |person| [person, Hash[subjects.map { |subject| [subject, 0] }]]}]

# output: => {"tom"=>{"math"=>0, "english"=>0},
# "mary"=>{"math"=>0, "english"=>0},
# "rob"=>{"math"=>0, "english"=>0}}
share|improve this question

2 Answers 2

While you can do this in one line, there's really no need. At least you can break the line into do..end blocks to keep things more readable.

But moreso, I'd consider duping the zeros hash (presumably grades for the subjects so I'll call it that) once you've built it, rather than build and re-build it for each person.

Also, you can use each_with_object (a sort of reduce) instead of wrapping a mapped array in Hash[..].

For instance,

grades = subjects.each_with_object({}) { |subject, hash| hash[subject] = 0 }
people.each_with_object({}) { |name, hash| hash[name] = grades.dup }

But there are many ways to go about this. While it's a little outside the task, you could simply initialize a Hash with default value of zero for unknown keys:

grades = Hash.new(0)
grades["math"] #=> 0
grades["no a real subject"] #=> 0

Of course, as the last line shows, you'll get zero for any key, even if it doesn't really make sense. Usually you'd of course just get a nil. You'll also want to be careful with this approach in general, if the default value is an object, since it won't automatically dup itself; it'll be the same object (same object reference) for every key. It's not a problem with numbers though.

Still, it'd condense things to one line again:

people.each_with_object({}) { |name, hash| hash[name] = Hash.new(0) }

A more precise alternative, that only creates the required keys, could be:

grades = Hash[ subjects.zip(Array.new(subjects.size, 0)) ]

And of course, recent versions of Ruby have a to_h method that lets you chain the call, rather than wrap an array in it:

grades = subjects.map { |subject| [subject, 0] }.to_h
people.map { |name| [name, grades.dup] }.to_h

Which is close to what you started with, just using dup.

There are even more ways to do things - this is just off the top of my head.

share|improve this answer

Conceptually, that's the way to go. I'd just propose those changes:

I'd write:

scores = people.map do |person|
  person_scores = subjects.map { |subject| [subject, 0] }.to_h 
  [person, person_scores]
end.to_h
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.