2009-08-05 4 views
5

Ich habe eine Anfänger-Ruby-Frage über multidimensionale Arrays.Wie macht man dynamische mehrdimensionale Arrays in Ruby?

Ich möchte Einträge nach Jahr und Monat sortieren. Deshalb möchte ich ein mehrdimensionales Array erstellen, die Jahre enthalten würde -> Monate -> Einträge von Monat

So würde das Array wie:

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

Jetzt habe ich:

@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 

aber wenn ich versuche, durch die Jahre Array zu durchlaufen, die ich erhalten: „nicht definierte Methode` jeder‘für das Jahr 2009: Fixnum“

auch versucht:

@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 

Antwort

11

Sie erhalten den Fehler, weil eine FixNum (dh eine Zahl) auf das Array in der Zeile @years.push(year) geschoben wird.

Ihr Ansatz, Arrays zu verwenden, ist ein wenig fehlerhaft; Ein Array ist perfekt, um eine geordnete Liste von Elementen zu halten. In Ihrem Fall haben Sie eine Zuordnung von Schlüsseln zu Werten, was perfekt für einen Hash ist.

In der ersten Ebene sind die Schlüssel Jahre, die Werte sind Hashwerte. Die Hashes der zweiten Ebene enthalten Schlüssel von Monaten und Werte von Feldern von Einträgen.

In diesem Fall ist eine typische Ausgabe des Codes wäre so etwas wie (basierend auf dem Beispiel) aussehen:

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

Beachten Sie, dass jedoch ist die Reihenfolge der Jahre und Monate in keiner sein garantiert bestimmte Reihenfolge. Die Lösung besteht normalerweise darin, die Schlüssel zu bestellen, wann immer Sie darauf zugreifen möchten. Nun wird ein Code, der eine solche Ausgabe erzeugen würde (basierend auf dem Layout-Code, kann, obwohl viel rubier gemacht werden):

@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 
+0

Vielen Dank für die mir erleuchtet . – jussi

+0

Gern geschehen. Schau dir Michael_Sepcots Antwort an, wie du es auf eine rubinischere Art machen kannst. –

+0

Danke! Endlich verstehe ich das –

4

Ich bin mit Hash-Tabellen anstelle von Arrays, weil ich denke, es wahrscheinlich macht hier mehr Sinn. Es ist jedoch ziemlich trivial, zu Arrays zurückzukehren, wenn Sie das bevorzugen.

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 

Die Ausgabe lautet: {2007=>{10=>[5]}, 2009=>{8=>[1, 2], 9=>[3]}}

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 
8

Sie können die verschachtelte Array-Struktur in einer Zeile erhalten, indem eine Kombination aus group_by s mit und map:

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

Ist nicht group_by ein Rails-Zusatz, nicht plain old ruby? –

+1

Nein, es kommt in Ruby 1.8.7. –

+0

Ah - Ich habe es auf Ruby 1.8.6 versucht. –