2011-02-07 29 views
7

Me están enviando un hash anidado que debe ordenarse por sus valores. Por ejemplo:Ordenar elementos en un hash anidado por sus valores

@foo = {"a"=>{"z"=>5, "y"=>3, "x"=>88}, "b"=>{"a"=>2, "d"=>-5}} 

Cuando se ejecuta el siguiente:

@foo["a"].sort{|a,b| a[1]<=>b[1]} 

me sale:

[["y", 3], ["z", 5], ["x", 88]] 

Esto es muy bueno, que es exactamente lo que quiero. El problema es que no siempre sabré cuáles son todas las claves que se me envían, así que necesito algún tipo de ciclo. Traté de hacer lo siguiente:

@foo.each do |e| 
    e.sort{|a,b| a[1]<=>b[1]} 
end 

Esto para mí tiene sentido ya que si llamo manualmente @ foo.first [0] consigo

"a" 

y @ foo.first [1] devuelve

{"z"=>5, "y"=>3, "x"=>8} 

pero por alguna razón esto no está ordenando correctamente (por ejemplo, en absoluto). Supongo que esto se debe a que cada uno llama ordena en todo el objeto hash en lugar de en los valores "a". ¿Cómo accedo a los valores del hash anidado sin saber cuál es su clave?

Respuesta

6

Es posible que desee bucle sobre el hash de esta manera:

 
@foo.each do |key, value| 
    @foo[key] = value.sort{ |a,b| a[1]<=>b[1] } 
end 
+0

1) es conceptualmente preferible utilizar Enumerable # sort_by en lugar de ordenar, verifique la respuesta de Phrogz. 2) esto hace que una operación in situ, la creación de nuevos objetos (enfoque funcional) hace que el código sea más fácil de seguir. – tokland

+0

@tokland: todos fueron excelentes puntos, pero mi objetivo era seguir el código OP mientras seguía resolviendo el problema real. Si bien la creación de nuevos objetos puede ser más fácil de seguir, es más claro no crear una variable temporal si se va a asignar de nuevo al original de todos modos. –

4
@foo = {"a"=>{"z"=>5, "y"=>3, "x"=>88}, "b"=>{"a"=>2, "d"=>-5}} 
@bar = Hash[ @foo.map{ |key,values| [ key, values.sort_by(&:last) ] } ] 

O, a través de un camino menos complicado:

@bar = {} 
@foo.each do |key,values| 
    @bar[key] = values.sort_by{ |key,value| value } 
end 

En ambos casos @bar resulta ser:

p @bar 
#=> { 
#=> "a"=>[["y", 3], ["z", 5], ["x", 88]], 
#=> "b"=>[["d", -5], ["a", 2]] 
#=> } 
+0

+1 pero, escribiría un getter explícito para sort_by: values.sort_by {| k, v | v}. ¿Veremos el día en que Ruby envía el Enumerable # to_hash absolutamente necesario? Estoy cansado de recomendar Facet's Enumberable # mash para algo que debería estar incorporado, suspiro ... @bar = @ foo.to_hash {| key, values ​​| [clave, values.sort_by {| k, v | v}]} – tokland

0

en su ejemplo e es una matriz temporal que contiene un par de [clave, valor]. En este caso, la clave del personaje y el hash anidado. Entonces, e.sort{|a,b|...} intentará comparar el carácter con el hash, y falla con un error de tiempo de ejecución. Creo que probablemente quisiste escribir e[1].sort{...}. Pero incluso eso no va a funcionar correctamente, porque no almacena el hash ordenado en ninguna parte: @foo.each devuelve el @foo original y lo deja sin cambios.

La mejor solución es la sugerida por @Pan Thomakos:

@foo.each do |key, value| 
    @foo[key] = value.sort{ |a,b| a[1]<=>b[1] } 
end 
1

Mi compañero de trabajo le ocurrió una solución ligeramente más flexible que forma recursiva ordenará una matriz de cualquier profundidad:

def deep_sort_by(&block) 
    Hash[self.map do |key, value| 
    [if key.respond_to? :deep_sort_by 
     key.deep_sort_by(&block) 
    else 
     key 
    end, 

    if value.respond_to? :deep_sort_by 
     value.deep_sort_by(&block) 
    else 
     value 
    end] 

    end.sort_by(&block)] 
end 

Puede inyectarlo en todos los valores hash y luego simplemente llamarlo así:

myMap.deep_sort_by { |obj| obj } 

The el código sería similar para una matriz. Nos published it as a gem para que otros lo utilicen, vea blog post para obtener más detalles.

Descargo de responsabilidad: I work for this company.

Cuestiones relacionadas