How to make dynamic multi-dimensional array in ruby?

12,651

Solution 1

You are getting the error because a FixNum (that is, a number) is pushed on the array, in the line that reads @years.push(year).

Your approach of using Arrays to start with is a bit flawed; an array is perfect to hold an ordered list of items. In your case, you have a mapping from keys to values, which is perfect for a Hash.

In the first level, the keys are years, the values are hashes. The second level's hashes contain keys of months, and values of arrays of entries.

In this case, a typical output of your code would look something like (based on your example):

{ 2009 => { 8 => [Entry1, Entry2], 9 => [Entry3] }, 2007 => { 10 => [Entry5] }}

Notice that, however, the order of years and months is not guaranteed to be in any particular order. The solution is normally to order the keys whenever you want to access them. Now, a code that would generate such an output (based on your layout of code, although can be made much rubier):

@years = {}
@entries.each do |entry|
  timeobj = Time.parse(entry.created_at.to_s)
  year = timeobj.strftime("%Y").to_i
  month = timeobj.strftime("%m").to_i
  @years[year] ||= {} # Create a sub-hash unless it already exists
  @years[year][month] ||= []
  @years[year][month] << entry
end

Solution 2

You can get the nested array structure in one line by using a combination of group_bys and map:

@entries.group_by {|entry| entry.created_at.year }.map { |year, entries| [year, entries.group_by {|entry| entry.created_at.month }] }

Solution 3

I'm using hash tables instead of arrays, because I think it probably makes more sense here. However, it's fairly trivial to change back to using arrays if that's what you prefer.

entries = [
    [2009, 8, 1],
    [2009, 8, 2],
    [2009, 9, 3],
    [2007, 10, 5]
]

years = Hash.new
entries.each { |e|
    year = e[0]
    month = e[1]
    entry = e[2]

    # Add to years array
    years[year] ||= Hash.new
    years[year][month] ||= Array.new
    years[year][month] << entry
}

puts years.inspect

The output is: {2007=>{10=>[5]}, 2009=>{8=>[1, 2], 9=>[3]}}

Solution 4

# create a hash of hashes of array
@years = Hash.new do |h,k|
  h[k] = Hash.new do |sh, sk|
    sh[sk] = []
  end
end

@entries.each do |entry|
  timeobj = Time.parse(entry.created_at.to_s)
  year = timeobj.year
  month = timeobj.month
  @years[year][month] << entry
end
Share:
12,651
Admin
Author by

Admin

Updated on June 26, 2022

Comments

  • Admin
    Admin almost 2 years

    I have a beginner ruby question about multi dimensional arrays.

    I want to sort entries by year and month. So I want to create a multi-dimensional array that would contain years -> months -> entries of month

    So the array would be like:

    2009 ->
           08
              -> Entry 1
              -> Entry 2
           09
              -> Entry 3
    2007 ->
           10
              -> Entry 5
    

    Now I have:

    @years = []
    @entries.each do |entry|
        timeobj = Time.parse(entry.created_at.to_s)
        year = timeobj.strftime("%Y").to_i
        month = timeobj.strftime("%m").to_i
        tmparr = []
        tmparr << {month=>entry}
        @years.push(year)
        @years << tmparr
    end
    

    but when I try to iterate through the years array, I get: "undefined method `each' for 2009:Fixnum"

    Tried also:

    @years = []
    @entries.each do |entry|
        timeobj = Time.parse(entry.created_at.to_s)
        year = timeobj.strftime("%Y").to_i
        month = timeobj.strftime("%m").to_i
        @years[year][month] << entry
    end
    
  • Admin
    Admin over 14 years
    Thanks for the enlightening me.
  • arun prakash
    arun prakash over 14 years
    You're welcome. Look at Michael_Sepcot's answer for how to do in a more rubyish way.
  • oligan
    oligan over 14 years
    Isn't group_by a rails addition, not plain old ruby?
  • Diego Somar
    Diego Somar almost 7 years
    Thanks! Finally I understand this