2008-10-08 12 views
24

Tengo una matriz de valores hash, y quiero los valores únicos de ella. Llamar al Array.uniq no me da lo que espero.¿Cómo obtengo los elementos únicos de una matriz de hashes en Ruby?

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

donde esperaba:

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

En la búsqueda alrededor en la red, no he venido para arriba con una solución que yo estaba contento con. La gente recomendaba redefinir Hash.eql? y Hash.hash, ya que eso es lo que Array.uniq está consultando.

Edit: Donde me topé con esto en el mundo real, los hashes fueron un poco más complejos. Eran el resultado de JSON analizado que tenía múltiples campos, algunos de los cuales también eran valores hash. Tenía una variedad de esos resultados que quería filtrar los valores únicos.

no me gusta el redefinen Hash.eql? y la solución Hash.hash, porque yo bien tienen 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.

Cambiando Hash globalmente tiene algún potencial, especialmente si se hizo temporalmente. Me gustaría crear otra clase o función de ayuda que cubra el ahorro de las definiciones antiguas y restaurarlas, pero creo que esto agrega más complejidad de la que realmente se necesita.

El uso de inject parece una buena alternativa a la redefinición de Hash.

Respuesta

27

puedo conseguir lo que quiero llamando 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}] 
+0

mucho más mejor, creo que el enlace que he publicado anteriormente – edthix

0

La respuesta que dan es similar a la discutida here. Anula los métodos hash y eql? en los valores hash que van a aparecer en la matriz, lo que hace que uniq se comporte correctamente.

+0

Esa es una de las soluciones que he encontrado en la red. No me gustó que necesitaba redefinir hash, solo para llamar a uniq. –

+0

Si las clases vanilla Hash y Array no hacen lo que necesita, debería considerar definir sus propias clases que implementen el comportamiento requerido. ¿Puedes describir qué es lo que estás tratando de modelar con matrices de hash? –

2

Asumiendo que su hash siempre son pares de valores clave individuales, esto va a funcionar:

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

Hash.to_a crea una matriz de matrices de valores clave, así que el primer mapa que obtiene:

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

uniq en matrices hace lo que quiere, que le da:

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

y luego el segundo mapa los vuelve a togeth er como hashes de nuevo.

+0

El problema del mundo real que encontré usa hashes más complejos. –

+0

No estoy seguro de por qué este voto fue rechazado, así que lo puse de nuevo. –

5

que he tenido una situación similar, pero hashes tenía llaves. Utilicé el método de clasificación.

lo que quiero decir:

tiene una matriz:

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

ordenar que (#sort_by {|t| t[:x]}) y conseguir esto:

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

ahora una versión modificada poco de respuesta 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í es 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:

le devolverá justo lo que habría esperado
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) 
17

Rubí 1.8.7+:

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

El método de tubería en matrices (disponible desde 1,8 .6) realiza una unión establecida (devolviendo una matriz), por lo que la siguiente es otra forma posible de obtener elementos únicos de cualquier matriz a:

[] | a

+0

esto no funciona para mí. –

+0

@SunnyRGupta, ¿qué versión de Ruby estás usando? – yoniLavi

+0

'ruby 1.9.3p448 (revisión de 2013-06-27 41675) [x86_64-darwin13.2.0]' –

1

Puede utilizar (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}] 
Cuestiones relacionadas