Pregunta

Tengo una serie de hashes, y quiero que los valores únicos salgan de ella. Llamar a Array.uniq no me da lo que espero.

a = [{:a => 1},{:a => 2}, {:a => 1}]
a.uniq # => [{:a => 1}, {:a => 2}, {:a => 1}]

Donde esperaba:

[{:a => 1}, {:a => 2}]

Al buscar en la red, no encontré una solución con la que estuviera contento. La gente recomendó redefinir Hash.eql? y Hash.hash , ya que eso es lo que Array.uniq está consultando.

Editar: Donde me encontré con esto en el mundo real, los hashes eran un poco más complejos. Fueron el resultado de JSON analizado que tenía varios campos, algunos de los cuales los valores también eran hashes. Tenía una serie de esos resultados que quería filtrar los valores únicos.

No me gusta la solución redefinir Hash.eql? y Hash.hash , porque tampoco tendría que redefinir Hash a nivel mundial , o redefinirlo para cada entrada en mi matriz. Cambiar la definición de Hash para cada entrada sería engorroso, especialmente porque puede haber hashes anidados dentro de cada entrada.

Cambiar Hash globalmente tiene cierto potencial, especialmente si se hizo temporalmente. Me gustaría crear otra clase o función de ayuda que envolviera guardar las definiciones anteriores y restaurarlas, pero creo que esto agrega más complejidad de la que realmente se necesita.

Utilizar inyectar parece ser una buena alternativa para redefinir Hash .

¿Fue útil?

Solución

Puedo obtener lo que quiero llamando a inject

a = [{:a => 1},{:a => 2}, {:a => 1}]
a.inject([]) { |result,h| result << h unless result.include?(h); result }

Esto devolverá:

[{:a=>1}, {:a=>2}]

Otros consejos

Ruby 1.8.7+ devolverá lo que esperaba:

[{:a=>1}, {:a=>2}, {:a=>1}].uniq
#=> [{:a=>1}, {:a=>2}] 

He tenido una situación similar, pero los hashes tenían claves. Usé el método de clasificación.

Lo que quiero decir:

tienes una matriz:

[{:x=>1},{:x=>2},{:x=>3},{:x=>2},{:x=>1}]

lo ordena ( #sort_by {| t | t [: x]} ) y obtiene esto:

[{:x=>1}, {:x=>1}, {:x=>2}, {:x=>2}, {:x=>3}]

ahora una versión de respuesta un poco modificada por Aaaron Hinni:

your_array.inject([]) do |result,item| 
  result << item if !result.last||result.last[:x]!=item[:x]
  result
end

También he intentado:

test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]}

pero es muy lento. Aquí está mi punto de referencia:

test=[]
1000.times {test<<{:x=>rand}}

Benchmark.bmbm do |bm|
  bm.report("sorting: ") do
    test.sort_by {|t| t[:x]}.inject([]) {|r,h| r<<h if !r.last||r.last[:x]!=h[:x]; r}
  end
  bm.report("inject: ") {test.inject([]) {|r,h| r<<h unless r.find {|t| t[:x]==h[:x]}; r}.sort_by {|t| t[:x]} }
end

resultados:

Rehearsal ---------------------------------------------
sorting:    0.010000   0.000000   0.010000 (  0.005633)
inject:     0.470000   0.140000   0.610000 (  0.621973)
------------------------------------ total: 0.620000sec

                user     system      total        real
sorting:    0.010000   0.000000   0.010000 (  0.003839)
inject:     0.480000   0.130000   0.610000 (  0.612438)

Suponiendo que sus hashes sean siempre pares clave-valor, esto funcionará:

a.map {|h| h.to_a[0]}.uniq.map {|k,v| {k => v}}

Hash.to_a crea una matriz de matrices de valor-clave, por lo que el primer mapa te muestra:

[[:a, 1], [:a, 2], [:a, 1]]

uniq en Arrays hace lo que quieres, dándote:

[[:a, 1], [:a, 2]]

y luego el segundo mapa los vuelve a unir como hashes nuevamente.

Puedes usar (probado en ruby ??1.9.3),

[{a: 1},{a: 2},{a:1}].uniq => [{a:1},{a: 2}]
[{a: 1,b: 2},{a: 2, b: 2},{a: 1, b: 3}].uniq_by {|v| v[:a]} => [{a: 1,b: 2},{a: 2, b: 2}]

La respuesta que das es similar a la que analizamos aquí . Anula los métodos hash y eql? en los hashes que aparecerán en la matriz, lo que hace que uniq se comporte correctamente.

El método de canalización en matrices (disponible desde 1.8.6) realiza la unión de conjuntos (devolviendo una matriz), por lo que la siguiente es otra forma posible de obtener elementos únicos de cualquier matriz a :

[] | a

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top