2009-06-13 8 views
1

Quelle est la "Rubyist" façon de faire la transformation de la structure de données suivantes:idiomatiques Ruby: la transformation de la structure de données

Je

 
    incoming = [ {:date => 20090501, :width => 2}, 
       {:date => 20090501, :height => 7}, 
       {:date => 20090501, :depth => 3}, 
       {:date => 20090502, :width => 4}, 
       {:date => 20090502, :height => 6}, 
       {:date => 20090502, :depth => 2}, 
       ] 

et je veux effondrer ce par: la date, de se retrouver avec

 
    outgoing = [ {:date => 20090501, :width => 2, :height => 7, :depth => 3}, 
       {:date => 20090502, :width => 4, :height => 6, :depth => 2}, 
       ] 

un tableau de tableaux serait également très bien à la dernière étape, à condition que les colonnes sont dans le même ordre dans chaque rangée. Aussi, surtout, je ne connais pas toutes les clés de hachage à l'avance (c'est-à-dire, je ne sais pas: largeur,: hauteur, ou: profondeur - elles pourraient être: les chats,: les chiens, et: les hamsters).

+1

Si l'ordre est important, vous ne voulez pas de tableaux * want *. S'appuyer sur l'ordre Hash est une mauvaise idée et ne fonctionne que dans Ruby 1.9. – Chuck

+0

Oui, mais si je garde la structure de hachage, alors mes clés me donneront l'ordre dont j'ai besoin.Je viens de faire le commentaire supplémentaire sur la commande pour le tableau de tableaux, car il n'y a aucun moyen de le remettre ensemble après le fait si j'ai un sous-tableau non ordonné. –

Répondre

8

Si vous utilisez Ruby 1.8.7 ou Ruby 1.9+ le code suivant se lit bien:

incoming.group_by{|hash| hash[:date]}.map do |_, hashes| 
    hashes.reduce(:merge) 
end 

Le trait de soulignement dans les attributs de bloc (_, hashes) indique que nous ne avons pas besoin/soins à ce sujet particulier attribut. #reduce est un alias pour #inject, qui est utilisé pour réduire une collection en un seul élément. Dans les nouvelles versions Ruby, il accepte également un symbole, qui est le nom de la méthode utilisée pour effectuer la réduction .

Il commence par appeler la méthode sur le premier élément de la collection avec le second élément comme argument. Il appelle ensuite la méthode à nouveau sur le résultat avec le troisième élément comme argument et ainsi de suite jusqu'à ce qu'il n'y ait plus d'éléments.

[1, 3, 2, 2].reduce(:+) => [4, 2, 2] => [6, 2] => 8 
+0

sympa ... ne connaissait pas la fonction group_by dans les rubis récents. – SztupY

0

Essayez ceci:

incoming = [ {:date => 20090501, :width => 2}, 
       {:date => 20090501, :height => 7}, 
       {:date => 20090501, :depth => 3}, 
       {:date => 20090502, :width => 4}, 
       {:date => 20090502, :height => 6}, 
       {:date => 20090502, :depth => 2}, 
       ] 

# Grouping by `:date` 
temp = {} 

incoming.each do |row| 
    if temp[row[:date]].nil? 
     temp[row[:date]] = [] 
    end 

    temp[row[:date]] << row 
end  

# Merging it together 
outcoming = []   

temp.each_pair do |date, hashlist| 
    res = {} 
    hashlist.each do |hash| 
     res.merge!(hash) 
    end 
    outcoming << res 
end 

Pour plus d'informations sur les hash -Membres, voir this page

Lors de la commande est importante, vous devez utiliser des tableaux déchiquetés:

incoming = [ {:date => 20090501, :width => 2}, 
       {:date => 20090501, :height => 7}, 
       {:date => 20090501, :depth => 3}, 
       {:date => 20090502, :width => 4}, 
       {:date => 20090502, :height => 6}, 
       {:date => 20090502, :depth => 2}, 
       ] 

# Grouping by `:date` 
temp = {} 

incoming.each do |row| 
    if temp[row[:date]].nil? 
     temp[row[:date]] = [] 
    end 
    key = row[:date] 
    row.delete :date 
    temp[key] << row 
end  

# Merging it together 
outcoming = []   

temp.each_pair do |date, hashlist| 
    res = [:date, date] 
    hashlist.each do |hash| 
     hash.each_pair {|key, value| res << [key, value] } 
    end 
    outcoming << res 
end 
2

est ici un doublure :)

incoming.inject({}){ |o,i| o[i[:date]]||=[];o[i[:date]]<<i;o}.map{|a| a[1].inject(){|o,i| o.merge(i)}} 

Mais en fait le post précédent est plus clair, et pourrait être plus rapide aussi.

EDIT: avec un peu d'optimisation:

p incoming.inject(Hash.new{|h,k| h[k]=[]}){ |o,i| o[i[:date]]<<i;o}.map{|a| a[1].inject(){|o,i| o.merge(i)}} 
+0

Explication: La première injection crée un hachage avec les dates en tant que clés et un tableau de hachages en tant que valeurs. Ensuite, la carte fusionnera tous ces hachages en un seul. – SztupY

+0

Nice quand même ;-) – Dario

2

Une solution concise:

incoming = [ {:date => 20090501, :width => 2}, 
      {:date => 20090501, :height => 7}, 
      {:date => 20090501, :depth => 3}, 
      {:date => 20090502, :width => 4}, 
      {:date => 20090502, :height => 6}, 
      {:date => 20090502, :depth => 2}, 
      ] 

temp = Hash.new {|hash,key| hash[key] = {}} 
incoming.each {|row| temp[row[:date]].update(row)} 
outgoing = temp.values.sort {|*rows| rows[0][:date] <=> rows[1][:date]} 

La seule chose qui est tout délicat ici est le constructeur de Hash, qui vous permet d'alimenter un bloc cela s'appelle quand vous accédez à une clé inexistante. Donc, j'ai le Hash créer un Hash vide pour que nous puissions le mettre à jour avec les valeurs que nous trouvons. Ensuite, je viens d'utiliser la date comme les clés de hachage, trier les valeurs de hachage par date et nous sommes transformés.

Questions connexes