2011-03-06 32 views
13

que tienen un hash de este modo:¿Cómo eliminar duplicados en un hash en Ruby on Rails?

[ 
    { 
    :lname => "Brown", 
    :email => "[email protected]", 
    :fname => "James" 
    }, 
    { 
    :lname => nil, 
    :email => "[email protected]", 
    :fname => nil 
    }, 
    { 
    :lname => "Smith", 
    :email => "[email protected]", 
    :fname => "Brad" 
    }, 
    { 
    :lname => nil, 
    :email => "[email protected]", 
    :fname => nil 
    }, 
    { 
    :lname => "Smith", 
    :email => "[email protected]", 
    :fname => "Brad" 
    }, 
    { 
    :lname => nil, 
    :email => "[email protected]", 
    :fname => nil 
    } 
] 

Lo que me gustaría aprender a hacerlo es la forma de eliminar un registro si es duplicado. Es decir, vea cómo hay varios "[email protected]", ¿cómo puedo eliminar los registros duplicados, es decir, eliminar todos los demás que tienen un correo electrónico de "[email protected]" ... Hacer que el correo electrónico sea la clave, no el otro ¿campos?

+2

¿Es el un hash rubí puro o un hash que representa los datos en realidad en la base de datos (por ejemplo, a través de ActiveRecord)? –

+1

¿por qué no poner validates_uniqueness_of el campo de correo electrónico? De esa forma, incluso si obtiene material duplicado en sus parámetros, no se guardará. también pone el error necesario de captura cuando falla el almacenamiento. – corroded

+0

Se está creando en base a una lista de CSV, donde los usuarios pueden ingresar correos electrónicos para invitar amigos – AnApprentice

Respuesta

16

sé que esto es un hilo viejo, pero Rails tiene un método de 'Enumerable' llamada 'index_by' que puede ser útil en este caso:

list = [ 
    { 
    :lname => "Brown", 
    :email => "[email protected]", 
    :fname => "James" 
    }, 
    { 
    :lname => nil, 
    :email => "[email protected]", 
    :fname => nil 
    }, 
    { 
    :lname => "Smith", 
    :email => "[email protected]", 
    :fname => "Brad" 
    }, 
    { 
    :lname => nil, 
    :email => "[email protected]", 
    :fname => nil 
    }, 
    { 
    :lname => "Smith", 
    :email => "[email protected]", 
    :fname => "Brad" 
    }, 
    { 
    :lname => nil, 
    :email => "[email protected]", 
    :fname => nil 
    } 
] 

Ahora usted puede conseguir las filas únicas de la siguiente manera:

list.index_by {|r| r[:email]}.values 

Para fusionar las filas con el mismo ID de correo electrónico.

list.group_by{|r| r[:email]}.map do |k, v| 
    v.inject({}) { |r, h| r.merge(h){ |key, o, n| o || n } } 
end 

personalizada pero eficiente método:

list.inject({}) do |r, h| 
    (r[h[:email]] ||= {}).merge!(h){ |key, old, new| old || new } 
    r 
end.values 
5

Si está poniendo esto directamente en la base de datos, simplemente use validates_uniqueness_of :email en su modelo. Vea el documentation for this.

Si necesita eliminarlos a partir del hash real antes de ser utilizados luego hacer:

emails = [] # This is a temporary array, not your results. The results are still in my_array 
my_array.delete_if do |item| 
    if emails.include? item[:email] 
    true 
    else 
    emails << item[:email] 
    false 
    end 
end 

ACTUALIZACIÓN:

Esto combinar el contenido de las entradas duplicadas

merged_list = {} 
my_array.each do |item| 
    if merged_list.has_key? item[:email] 
    merged_list[item.email].merge! item 
    else 
    merged_list[item.email] = item 
    end 
end 
my_array = merged_list.collect { |k, v| v } 
+0

gracias, pero ¿cómo funcionaría esto? No quiero perder toda la otra información. Quiero tomar el hash anterior y eliminar los duplicados, manteniendo los nombres fname y lname. – AnApprentice

+2

¿De verdad quiere _merge_ las entradas con la misma dirección de correo electrónico?Eso es diferente a eliminar duplicados, que es lo que pediste. –

+0

no se fusiona solo elimina cualquier duplicado basado en una clave de correo electrónico. Puede ser poco inteligente y solo tomar los primeros [email protected] y luego eliminar el resto si existen duplicados basados ​​únicamente en el correo electrónico. – AnApprentice

21

En Ruby 1.9.2, Array#uniq aceptará un parámetro de bloqueo que utilizará al comparar sus objetos:

arrays.uniq { |h| h[:email] } 
+0

disparar No estoy en ruby ​​1.9.2 – AnApprentice

+0

@AnApprentice Puedes usar la gema backports y 'require 'backports/1.9.2/array/uniq''. –

1

Ok, esto (eliminar duplicados) es lo que pidieron:

a.sort_by { |e| e[:email] }.inject([]) { |m,e| m.last.nil? ? [e] : m.last[:email] == e[:email] ? m : m << e } 

pero creo que esto (fusionar los valores) es lo que quiere:

a.sort_by { |e| e[:email] }.inject([]) { |m,e| m.last.nil? ? [e] : m.last[:email] == e[:email] ? (m.last.merge!(e) { |k,o,n| o || n }; m) : m << e } 

Tal vez' Estoy estirando la idea de una línea un poco irrazonable, por lo que con un formato diferente y un caso de prueba:

Aiko:so ross$ cat mergedups 
require 'pp' 

a = [{:fname=>"James", :lname=>"Brown", :email=>"[email protected]"}, 
    {:fname=>nil,  :lname=>nil,  :email=>"[email protected]"}, 
    {:fname=>"Brad", :lname=>"Smith", :email=>"[email protected]"}, 
    {:fname=>nil,  :lname=>nil,  :email=>"[email protected]"}, 
    {:fname=>"Brad", :lname=>"Smith", :email=>"[email protected]"}, 
    {:fname=>"Brad", :lname=>"Smith", :email=>"[email protected]"}] 

pp(
    a.sort_by { |e| e[:email] }.inject([]) do |m,e| 
    m.last.nil? ? [e] : 
     m.last[:email] == e[:email] ? (m.last.merge!(e) { |k,o,n| o || n }; m) : 
     m << e 
    end 
) 
Aiko:so ross$ ruby mergedups 
[{:email=>"[email protected]", :fname=>"Brad", :lname=>"Smith"}, 
{:email=>"[email protected]", :fname=>"James", :lname=>"Brown"}] 
+0

Eso es genial solo deseo saber cómo hizo lo que está haciendo. Para puntos extra un pequeño comentario – AnApprentice

+0

¿Qué exactamente '.inject ([])' hacer? –

+2

@AnApprentice: seguro, no hay problema. '# injectject' es un método en' Enumerable' que es implementado por 'Array'. De esta forma, recorre la matriz produciendo un objeto * memo * y * element * en el bloque, que devuelve * memo * para la siguiente iteración. Entonces, después del sort_by, simplemente comparo cada hash con el último en la última nota y combino los campos si los correos electrónicos coinciden, de lo contrario solo vi el elemento al final de la nota, que en última instancia es lo que 'inject' devolverá como el valor de la expresión. – DigitalRoss