Trier le hachage par clé, retourner le hachage en Ruby

258

Serait-ce la meilleure façon de trier un hachage et de renvoyer un objet Hash (au lieu d'un tableau):

h = {"a"=>1, "c"=>3, "b"=>2, "d"=>4}
# => {"a"=>1, "c"=>3, "b"=>2, "d"=>4}

Hash[h.sort]
# => {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
Vincent
la source
9
Je ne suis pas sûr qu'il y ait beaucoup d'avantages à trier un hachage, à moins que vous ne l'utilisiez eachou que each_pairvous le répétiez. Même alors, j'attraperais probablement les clés, les trierais, puis les répéterais en saisissant les valeurs selon les besoins. Cela garantit que le code se comportera correctement sur les anciens Rubis.
The Tin Man
Est également logique dans ruby ​​1.9. J'avais une collection de rendez-vous regroupés par dates (sous forme de clés) provenant de db et j'ai trié manuellement par rubis. Par exemple. {"2012-09-22": [...], "2012-09-30": [...], "2012-10-12": [...]}
Adit Saxena
Oui, je trouve que votre processus Hash [h.sort] est plus efficace que le tri des clés, puis que vous accédez à nouveau au hachage via les clés triées.
Douglas
3
Vous avez eu quelques années pour réfléchir à votre solution, êtes-vous prêt à accepter une réponse? ;-)
Mark Thomas

Réponses:

219

Dans Ruby 2.1, c'est simple:

h.sort.to_h
Mark Thomas
la source
@zachaysan mais ça marche: h.sort{|a,z|a<=>z}.to_h(testé 2.1.10, 2.3.3)
whitehat101
@ whitehat101 Vous avez raison. J'ai eu un bug ( ac'est un tableau, pas seulement la clé). J'ai supprimé mon commentaire.
zachaysan
Juste au cas où quelqu'un d' autre est à la recherche d'un moyen de trier un tableau de hash, cela fera l'affaire (où h est le tableau): h.map(&:sort).map(&:to_h).
JM Janzen
82

Remarque: Ruby> = 1.9.2 a un hachage préservant l'ordre: les clés de commande insérées seront l'ordre dans lequel elles sont énumérées. Ce qui suit s'applique aux anciennes versions ou au code rétrocompatible.

Il n'y a pas de concept de hachage trié. Alors non, ce que tu fais n'est pas bien.

Si vous souhaitez le trier pour l'affichage, renvoyez une chaîne:

"{" + h.sort.map{|k,v| "#{k.inspect}=>#{v.inspect}"}.join(", ") + "}"

ou, si vous voulez les clés dans l'ordre:

h.keys.sort

ou, si vous souhaitez accéder aux éléments dans l'ordre:

h.sort.map do |key,value|
  # keys will arrive in order to this block, with their associated value.
end

mais en résumé, cela n'a aucun sens de parler d'un hachage trié. D'après les documents , "L'ordre dans lequel vous parcourez un hachage par clé ou par valeur peut sembler arbitraire et ne sera généralement pas dans l'ordre d'insertion." Ainsi, l'insertion de clés dans un ordre spécifique dans le hachage n'aidera pas.

Peter
la source
C'est correct. Je suggère d'utiliser la gemme RBtree pour obtenir la fonctionnalité d'ensemble ordonné en rubis.
Aaron Scruggs
26
À partir de 1.9.2, l'ordre d'insertion de hachage sera conservé. Voir redmine.ruby-lang.org/issues/show/994
David
4
"À partir de la version 1.9.2, l'ordre d'insertion de hachage sera conservé.", Et c'est adorable.
The Tin Man
2
Concernant mon premier commentaire (se sentir drôle): Par exemple, s'appuyer sur l'ordre de hachage se briserait en silence et de manière imprévisible pour les versions Ruby antérieures à 1.9.2.
Jo Liss
5
Comment cette réponse a-t-elle obtenu environ 20 + 1 sans répondre à aucune des deux parties de la question OP? "1) Serait-ce (exemple OP) le meilleur moyen de trier un hachage, 2) et de renvoyer un objet Hash"? Je n'envie pas les +1 :) c'est juste après cette lecture de la réponse que je reste avec les questions originales. Aussi, si le fait est qu'il n'y a pas de hachage trié, regardez les commentaires pour la réponse
choisie
64

Je l'ai toujours utilisé sort_by. Vous devez envelopper la #sort_bysortie avec Hash[]pour en faire une sortie de hachage, sinon elle génère un tableau de tableaux. Alternativement, pour ce faire, vous pouvez exécuter la #to_hméthode sur le tableau de tuples pour les convertir en une k=>vstructure (hachage).

hsh ={"a" => 1000, "b" => 10, "c" => 200000}
Hash[hsh.sort_by{|k,v| v}] #or hsh.sort_by{|k,v| v}.to_h

Il y a une question similaire dans " Comment trier un hachage Ruby par valeur numérique? ".

boulder_ruby
la source
8
l'utilisation sort_byd'un hachage renvoie un tableau. Vous devrez à nouveau le mapper comme un hachage. Hash[hsh.sort_by{|k,v| v}]
stevenspiel
1
oui, la classe d'énumérateur interprète les hachages comme des tableaux, je pense
boulder_ruby
3
, Est en quelque sorte droit sur les valeurs: hsh.sort_by(&:last).to_h => {"b"=>10, "a"=>1000, "c"=>200000}.
Cary Swoveland
1
Notez que l'appel to_hn'est pris en charge que dans Ruby 2.1.0+
Phrogz
1
il y a une faute de frappe dans le commentaire, correction:sort_by{|k,v| v}.to_h)
gigue le
13

Non, ce n'est pas le cas (Ruby 1.9.x)

require 'benchmark'

h = {"a"=>1, "c"=>3, "b"=>2, "d"=>4}
many = 100_000

Benchmark.bm do |b|
  GC.start

  b.report("hash sort") do
    many.times do
      Hash[h.sort]
    end
  end

  GC.start

  b.report("keys sort") do
    many.times do
      nh = {}
      h.keys.sort.each do |k|
        nh[k] = h[k]
      end
    end
  end
end

       user     system      total        real
hash sort  0.400000   0.000000   0.400000 (  0.405588)
keys sort  0.250000   0.010000   0.260000 (  0.260303)

Pour les gros hachages, la différence augmentera jusqu'à 10 fois et plus

fl00r
la source
12

Vous avez donné la meilleure réponse à vous-même dans l'OP: Hash[h.sort]si vous avez envie de plus de possibilités, voici une modification en place du hachage d'origine pour le trier:

h.keys.sort.each { |k| h[k] = h.delete k }
Boris Stitnicky
la source
1
Pour les curieux, cela fonctionne un peu plus vite que le "tri des clés" dans stackoverflow.com/a/17331221/737303 sur Ruby 1.9.3.
azote
9

Trier le hachage par clé , retourner le hachage en Ruby

Avec déstructuration et tri Hash #

hash.sort { |(ak, _), (bk, _)| ak <=> bk }.to_h

Enumerable # sort_by

hash.sort_by { |k, v| k }.to_h

Tri Hash # avec comportement par défaut

h = { "b" => 2, "c" => 1, "a" => 3  }
h.sort         # e.g. ["a", 20] <=> ["b", 30]
hash.sort.to_h #=> { "a" => 3, "b" => 2, "c" => 1 }

Remarque: <Ruby 2.1

array = [["key", "value"]] 
hash  = Hash[array]
hash #=> {"key"=>"value"}

Remarque:> Ruby 2.1

[["key", "value"]].to_h #=> {"key"=>"value"}
Moriarty
la source
1
si vous ne l'utilisez pas, vvous devez préfixer un trait de soulignementhash.sort_by { |k, _v| k }.to_h
silva96
6

ActiveSupport :: OrderedHash est une autre option si vous ne voulez pas utiliser ruby ​​1.9.2 ou lancer vos propres solutions de contournement.

ermite
la source
Le lien est rompu
Snake Sanders
3
J'ai corrigé le lien pour pointer vers Google au cas où un historien voudrait rechercher comment cela aurait pu être fait dans le passé. Mais quiconque vient à ce sujet maintenant devrait utiliser une version plus récente de Ruby.
eremite
0
@ordered = {}
@unordered.keys.sort.each do |key|
  @ordered[key] = @unordered[key]
end

la source
4
Voici comment procéder si ruby ​​n'avait pas de méthodes comme Hash # sort_by
boulder_ruby
0

J'ai eu le même problème (j'ai dû trier mes équipements par leur nom) et j'ai résolu comme ceci:

<% @equipments.sort.each do |name, quantity| %>
...
<% end %>

@equipments est un hachage que je construis sur mon modèle et que je retourne sur mon contrôleur. Si vous appelez .sort, il triera le hachage en fonction de sa valeur clé.

Gabriel Mesquita
la source
-3

J'ai aimé la solution dans le post précédent.

J'ai fait un mini-cours, je l'ai appelé class AlphabeticalHash. Il a également une méthode appelée ap, qui accepte un argument, une Hash, en entrée: ap variable. Similaire à pp ( pp variable)

Mais il va (essayer et) imprimer dans la liste alphabétique (ses clés). Ne sais pas si quelqu'un d'autre veut l'utiliser, il est disponible en tant que gemme, vous pouvez l'installer comme tel:gem install alphabetical_hash

Pour moi, c'est assez simple. Si d'autres ont besoin de plus de fonctionnalités, faites-le moi savoir, je les inclurai dans la gemme.

EDIT: Le mérite revient à Peter , qui m'a donné l'idée. :)

shevy
la source