Ordenar hash por clave, devolver hash en Ruby

258

¿Sería esta la mejor manera de ordenar un hash y devolver un objeto Hash (en lugar de 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}
Vincent
fuente
99
No estoy seguro de que haya mucha ventaja en ordenar un hash, a menos que lo esté utilizando eacho each_pairpara iterar sobre él. Incluso entonces, probablemente aún agarraría las claves, las ordenaría, luego las repetiría agarrando los valores según sea necesario. Eso asegura que el código se comportará correctamente en Rubíes más antiguos.
The Tin Man
Tiene sentido en ruby ​​1.9 también. Tenía una colección de citas agrupadas por fechas (como claves) provenientes de db y las clasifiqué manualmente a través de ruby. P.ej. {"2012-09-22": [...], "2012-09-30": [...], "2012-10-12": [...]}
Adit Saxena
Sí, creo que su proceso Hash [h.sort] es más efectivo que ordenar las claves y luego acceder nuevamente al hash a través de las claves ordenadas.
Douglas
" ¿Cuál es la forma más rápida de ordenar un Hash? Será útil.
El Hombre de hojalata
3
Has tenido algunos años para pensar en tu solución, ¿estás listo para aceptar una respuesta? ;-)
Mark Thomas

Respuestas:

219

En Ruby 2.1 es simple:

h.sort.to_h
Mark Thomas
fuente
@zachaysan pero funciona: h.sort{|a,z|a<=>z}.to_h(probado 2.1.10, 2.3.3)
whitehat101
@ whitehat101 Tienes razón. Tuve un error ( aes una matriz, no solo la clave). He eliminado mi comentario
zachaysan
Sólo en caso de que otra persona está buscando una manera de ordenar una matriz de valores hash, esto va a hacer el truco (donde h es la matriz): h.map(&:sort).map(&:to_h).
JM Janzen
82

Nota: Ruby> = 1.9.2 tiene un hash para preservar el orden: las claves de orden que se inserten serán el orden en que se enumeran. Lo siguiente se aplica a versiones anteriores o al código compatible con versiones anteriores.

No existe el concepto de un hash ordenado. Entonces no, lo que estás haciendo no está bien.

Si desea ordenarlo para su visualización, devuelva una cadena:

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

o, si quieres las llaves en orden:

h.keys.sort

o, si desea acceder a los elementos en orden:

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

pero en resumen, no tiene sentido hablar de un hash ordenado. De los documentos , "El orden en que atraviesas un hash por clave o valor puede parecer arbitrario, y generalmente no estará en el orden de inserción". Por lo tanto, insertar claves en un orden específico en el hash no ayudará.

Peter
fuente
Esto es correcto. Sugeriría usar la gema RBtree para obtener la funcionalidad de conjunto ordenado en ruby.
Aaron Scruggs
26
A partir del 1.9.2 se conservará el orden de inserción hash. Ver redmine.ruby-lang.org/issues/show/994
David
44
"A partir del orden de inserción hash 1.9.2 se conservará", y es genial.
The Tin Man
2
Re mi primer comentario (se siente divertido): por ejemplo, confiar en el pedido de hash se rompería silenciosa e impredeciblemente para las versiones de Ruby anteriores a 1.9.2.
Jo Liss
55
¿Cómo obtuvo esta respuesta alrededor de 20 + 1 sin responder ni una de las dos partes de la pregunta OP? "1) ¿Sería (el ejemplo OP) la mejor manera de ordenar un hash, 2) y devolver el objeto Hash"? No envidio los +1 :) es justo después de leer la respuesta que aún me quedan las preguntas originales. Además, si el punto es que no existe un hash ordenado, mire los comentarios para la respuesta elegida a esta pregunta stackoverflow.com/questions/489139/…
jj_
64

Siempre he usado sort_by. Debe ajustar la #sort_bysalida Hash[]para que genere un hash, de lo contrario, genera una matriz de matrices. Alternativamente, para lograr esto, puede ejecutar el #to_hmétodo en la matriz de tuplas para convertirlas en una k=>vestructura (hash).

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

Hay una pregunta similar en " ¿Cómo ordenar un Ruby Hash por valor numérico? ".

boulder_ruby
fuente
8
usar sort_byen un hash devolverá una matriz. Tendría que trazarlo como un hash nuevamente. Hash[hsh.sort_by{|k,v| v}]
stevenspiel
1
Sí, creo que la clase enumeradora interpreta los hashes como matrices
boulder_ruby
3
Derecho, una especie está en valores: hsh.sort_by(&:last).to_h => {"b"=>10, "a"=>1000, "c"=>200000}.
Cary Swoveland
1
Tenga en cuenta que las llamadas to_hsolo se admiten en Ruby 2.1.0+
Phrogz,
1
hay un error tipográfico en el comentario, corrección:sort_by{|k,v| v}.to_h)
jitter
13

No, no lo es (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)

Para grandes hashes, la diferencia crecerá hasta 10 veces y más

fl00r
fuente
12

Te diste la mejor respuesta en el OP: Hash[h.sort]si anhelas más posibilidades, aquí hay una modificación en el lugar del hash original para ordenarlo:

h.keys.sort.each { |k| h[k] = h.delete k }
Boris Stitnicky
fuente
1
Para los curiosos, esto funciona un poco más rápido que el "ordenamiento de claves" en stackoverflow.com/a/17331221/737303 en Ruby 1.9.3.
nitrógeno
9

Ordenar hash por clave , devolver hash en Ruby

Con desestructuración y Hash # sort

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

Enumerable # sort_by

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

Hash # sort con comportamiento predeterminado

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"}
Moriarty
fuente
1
si no está usando v, debe hash.sort_by { |k, _v| k }.to_h
poner
6

ActiveSupport :: OrderedHash es otra opción si no desea usar ruby ​​1.9.2 o usar sus propias soluciones.

eremita
fuente
El enlace está roto
Snake Sanders
3
Arreglé el enlace para que apunte a Google en caso de que algún historiador quiera investigar cómo podría haberse hecho en el pasado. Pero cualquiera que esté llegando a esto ahora debería estar usando una versión más reciente de Ruby.
eremite
0
@ordered = {}
@unordered.keys.sort.each do |key|
  @ordered[key] = @unordered[key]
end
beornborn
fuente
44
Así es como haría esto si Ruby no tuviera métodos como Hash # sort_by
boulder_ruby
0

Tuve el mismo problema (tuve que ordenar mis equipos por su nombre) y resolví así:

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

@equipments es un hash que construyo en mi modelo y devuelvo en mi controlador. Si llama a .sort, ordenará el hash en función de su valor clave.

Gabriel Mesquita
fuente
-3

Me gustó la solución en la publicación anterior.

Hice una mini clase, la llamé class AlphabeticalHash. También cuenta con un método llamado ap, que acepta un argumento, un Hash, como entrada: ap variable. Similar a pp ( pp variable)

Pero (intentará) imprimir en una lista alfabética (sus teclas). No sé si alguien más quiere usar esto, está disponible como una gema, puede instalarlo como tal:gem install alphabetical_hash

Para mí, esto es bastante simple. Si otros necesitan más funcionalidad, hágamelo saber, lo incluiré en la gema.

EDITAR: El crédito va a Peter , quien me dio la idea. :)

shevy
fuente