Chen Kinnrot
Chen Kinnrot

Reputation: 21025

Smarter way to initialize a deep hash

I need a hash with one key per wday hour minute. I wrote this:

result = {}
      for wday in 1..7 do
        result[wday] = {}
        for hour in 0..23 do
          result[wday][hour] = {}
          for minute in 0..59 do
            result[wday][hour][minute] = 0
          end
        end
      end

I wanted to know if there is a better way to achieve the same behavior with less lines of code, and be as readable as is.

Upvotes: 1

Views: 88

Answers (5)

Ja͢ck
Ja͢ck

Reputation: 173612

It's not the prettiest of the lot, but here's something to consider:

(1..7).to_a.product((0..23).to_a, (0..59).to_a).
  reduce({}) do |hash, (weekday, hour, minute)| 
    hash.tap { |h| ((h[weekday] ||= {})[hour] ||= {})[minute] = 0 }
  end

Upvotes: 1

Cary Swoveland
Cary Swoveland

Reputation: 110695

I would do it thusly, which is merely a refinement of what the OP has written.

result = (1..7).each_with_object({}) do |wday, f|
           f[wday] = (0..23).each_with_object({}) do |hour, g|
             g[hour] = (0..59).each_with_object({}) do |minute, h|
               h[minute] = 0
             end
           end
         end

Alternatively,

result = (1..7).each_with_object({}) do |wday, f|
           (0..23).each_with_object(f[wday] = {}) do |hour, g|
             (0..59).each_with_object(g[hour] = {}) do |minute, h|
               h[minute] = 0
             end
           end
         end

Upvotes: 0

kairos
kairos

Reputation: 103

You can use a more readable set of instructions like this:

result = {}
(1..7).each do |wday|
  (0..23).each do |hour|
    (0..59).each do |minute|
      result[wday] ||= {}
      result[wday][hour] ||= {}
      result[wday][hour][minute] = 0
    end
  end
end

Even if there are more powerful solutions, I think that this is more readable if you don't need a more complex behaviour.

Upvotes: 0

Ilya
Ilya

Reputation: 13487

You can use default procs as possible way:

hash = Hash.new do |h, k|
  h[k] = Hash.new do |h1, k1|
    h1[k1] = Hash.new do |h2, k2|
      h2[k2] = 0 if (0..59).include?(k2)
    end if (0..23).include?(k1)
  end if (1..7).include?(k)
end

Then check:

hash[1][2][3]
#=> 0
hash[1][2][300]
#=> nil

The main advantage of this solution is that you don't need to create a bunch of objects, you just create a rule. Creating objects is possible if you have a little amount of objects like in your instance, but could be a problem if you want to create a lot of them.

Upvotes: 4

DjezzzL
DjezzzL

Reputation: 845

Could you try this one:

(1..7).inject({}) do |wday, i|
  wday[i] = (0..23).inject({}) do |hour, j|
    hour[j] = (0..59).inject({}) do |minute, k|
      minute[k] = 0
      minute
    end
    hour
  end
  wday
end

or something like that:

Hash.new(Hash.new(Hash.new(0))

which define default constructors for your nested hash properly

Upvotes: 0

Related Questions