Ordina l'hash per chiave, restituisce l'hash in Ruby


258

Sarebbe questo il modo migliore per ordinare un hash e restituire un oggetto Hash (anziché Array):

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}

9
Non sono sicuro che ci sia un grande vantaggio nell'ordinare un hash, a meno che tu non lo stia usando eacho each_pairper iterarlo. Anche allora, probabilmente afferroi ancora le chiavi, le ordinerei, quindi ripeterei su di esse afferrando i valori secondo necessità. Ciò garantisce che il codice si comporterà correttamente sui rubini più vecchi.
Tin Man il

Ha senso anche in ruby ​​1.9. Avevo una raccolta di appuntamenti raggruppati per date (come chiavi) provenienti da db e ho smistato manualmente tramite ruby. Per esempio. {"2012-09-22": [...], "2012-09-30": [...], "2012-10-12": [...]}
Adit Saxena,

Sì, trovo che il tuo processo Hash [h.sort] sia più efficace rispetto all'ordinamento delle chiavi, quindi accedendo di nuovo all'hash attraverso le chiavi ordinate.
Douglas,


3
Hai avuto qualche anno per pensare alla tua soluzione, sei pronto ad accettare una risposta? ;-)
Mark Thomas,

Risposte:


219

In Ruby 2.1 è semplice:

h.sort.to_h

@zachaysan ma funziona: h.sort{|a,z|a<=>z}.to_h(testato 2.1.10, 2.3.3)
whitehat101

@ whitehat101 Hai ragione. Ho avuto un bug ( aè un array, non solo la chiave). Ho cancellato il mio commento.
Zachaysan,

Nel caso in cui qualcun altro è alla ricerca di un modo per ordinare un array di hash, questo farà il trucco (dove h è la matrice): h.map(&:sort).map(&:to_h).
JM Janzen,

82

Nota: Ruby> = 1.9.2 ha un hash di conservazione dell'ordine: le chiavi dell'ordine inserite saranno nell'ordine in cui sono elencate. Quanto segue si applica alle versioni precedenti o al codice compatibile con le versioni precedenti.

Non esiste un concetto di hash ordinato. Quindi no, quello che stai facendo non è giusto.

Se lo desideri ordinato per la visualizzazione, restituisci una stringa:

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

oppure, se si desidera che le chiavi siano in ordine:

h.keys.sort

oppure, se si desidera accedere agli elementi in ordine:

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

ma in sintesi, non ha senso parlare di un hash ordinato. Dai documenti , "L'ordine in cui attraversi un hash per chiave o valore può sembrare arbitrario e generalmente non sarà nell'ordine di inserimento". Quindi l'inserimento di chiavi in ​​un ordine specifico nell'hash non aiuta.


Questo è corretto. Suggerirei di utilizzare la gemma RBtree per ottenere la funzionalità set ordinata in rubino.
Aaron Scruggs,

26
A partire dall'ordine di inserimento hash 1.9.2 verrà conservato. Vedi redmine.ruby-lang.org/issues/show/994
David

4
"L'avvio dell'ordine di inserimento hash 1.9.2 verrà conservato.", Ed è dolce.
Tin Man,

2
Per quanto riguarda il mio primo commento (divertente): ad esempio, fare affidamento sull'ordinamento hash si romperà in modo silenzioso e imprevedibile per le versioni di Ruby precedenti alla 1.9.2.
Jo Liss,

5
In che modo questa risposta ha ottenuto circa 20 + 1 senza rispondere nemmeno a una delle due parti della domanda OP? "1) Quello (esempio OP) sarebbe il modo migliore per ordinare un hash, 2) e restituire un oggetto Hash"? Non invidio + 1 :) è solo dopo aver letto la risposta che mi rimangono ancora le domande originali. Anche se il punto è che non c'è cosa come un aspetto ordinato hash i commenti per la risposta a questa domanda scelto stackoverflow.com/questions/489139/...
jj_

64

L'ho sempre usato sort_by. È necessario racchiudere l' #sort_byoutput Hash[]per renderlo un hash, altrimenti emette un array di array. In alternativa, per eseguire ciò è possibile eseguire il #to_hmetodo sull'array di tuple per convertirle in una k=>vstruttura (hash).

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

C'è una domanda simile in " Come ordinare un Ruby Hash per valore numerico? ".


8
l'utilizzo di sort_byun hash restituirà un array. Dovresti mapparlo di nuovo come hash. Hash[hsh.sort_by{|k,v| v}]
Stevenspiel,

1
sì, la classe dell'enumeratore interpreta gli hash come array credo
boulder_ruby

3
A destra, una specie è sui valori: hsh.sort_by(&:last).to_h => {"b"=>10, "a"=>1000, "c"=>200000}.
Cary Swoveland,

1
Nota che la chiamata to_hè supportata solo in Ruby 2.1.0+
Phrogz,

1
c'è un errore di battitura nel commento, correzione:sort_by{|k,v| v}.to_h)
jitter

13

No, non lo è (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)

Per i grandi hash la differenza aumenterà fino a 10 volte e più


12

Hai dato la migliore risposta a te stesso nel PO: Hash[h.sort]se desideri più possibilità, ecco una modifica sul posto dell'hash originale per renderlo ordinato:

h.keys.sort.each { |k| h[k] = h.delete k }

1
Per i curiosi, questo è leggermente più veloce del "sort sort" in stackoverflow.com/a/17331221/737303 su Ruby 1.9.3.
azoto

9

Ordina l'hash per chiave , restituisce l'hash in Ruby

Con destrutturazione e Hash # sort

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

Enumerable # sort_by

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

Hash # sort con comportamento predefinito

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 }

Nota: <Ruby 2.1

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

Nota:> Ruby 2.1

[["key", "value"]].to_h #=> {"key"=>"value"}

1
se non lo usi vdovresti hash.sort_by { |k, _v| k }.to_h
aggiungere

6

ActiveSupport :: OrderedHash è un'altra opzione se non vuoi usare ruby ​​1.9.2 o lanciare le tue soluzioni alternative.


Il collegamento è interrotto
Snake Sanders,

3
Ho corretto il link per indicare a Google nel caso in cui uno storico volesse ricercare come ciò avrebbe potuto essere fatto in passato. Ma chiunque arrivi ora dovrebbe usare una versione più recente di Ruby.
eremite

0
@ordered = {}
@unordered.keys.sort.each do |key|
  @ordered[key] = @unordered[key]
end

4
È così che faresti se Ruby non avesse metodi come Hash # sort_by
boulder_ruby

0

Ho avuto lo stesso problema (ho dovuto ordinare le mie apparecchiature in base al loro nome) e ho risolto in questo modo:

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

@equipments è un hash che costruisco sul mio modello e restituisco sul mio controller. Se chiami .sort ordinerà l'hash in base al suo valore chiave.


-3

Mi è piaciuta la soluzione nel post precedente.

Ho fatto una mini-classe, l'ho chiamata class AlphabeticalHash. Essa ha anche un metodo chiamato ap, che accetta un argomento, un Hash, come input: ap variable. Simile a pp (pp variable )

Ma verrà (provare e) stampare nell'elenco alfabetico (i suoi tasti). Non so se qualcun altro vuole usarlo, è disponibile come gemma, è possibile installarlo come tale:gem install alphabetical_hash

Per me, questo è abbastanza semplice. Se altri hanno bisogno di più funzionalità, fammi sapere, lo includerò nella gemma.

EDIT: Il merito va a Peter , che mi ha dato l'idea. :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.