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}

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


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

@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á.


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? ".


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


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 }

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"}

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.


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

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.


-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. :)

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.