Aquí hay dos formas más de encontrar un duplicado.
Usar un conjunto
require 'set'
def find_a_dup_using_set(arr)
s = Set.new
arr.find { |e| !s.add?(e) }
end
find_a_dup_using_set arr
#=> "hello"
Usar select
en lugar defind
para devolver una matriz de todos los duplicados.
Utilizar Array#difference
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
def find_a_dup_using_difference(arr)
arr.difference(arr.uniq).first
end
find_a_dup_using_difference arr
#=> "hello"
soltar .first
para devolver una matriz de todos los duplicados.
Ambos métodos regresan nil
si no hay duplicados.
I propuse queArray#difference
ser añadido al núcleo Ruby. Más información está en mi respuesta aquí .
Punto de referencia
Comparemos los métodos sugeridos. Primero, necesitamos una matriz para probar:
CAPS = ('AAA'..'ZZZ').to_a.first(10_000)
def test_array(nelements, ndups)
arr = CAPS[0, nelements-ndups]
arr = arr.concat(arr[0,ndups]).shuffle
end
y un método para ejecutar los puntos de referencia para diferentes matrices de prueba:
require 'fruity'
def benchmark(nelements, ndups)
arr = test_array nelements, ndups
puts "\n#{ndups} duplicates\n"
compare(
Naveed: -> {arr.detect{|e| arr.count(e) > 1}},
Sergio: -> {(arr.inject(Hash.new(0)) {|h,e| h[e] += 1; h}.find {|k,v| v > 1} ||
[nil]).first },
Ryan: -> {(arr.group_by{|e| e}.find {|k,v| v.size > 1} ||
[nil]).first},
Chris: -> {arr.detect {|e| arr.rindex(e) != arr.index(e)} },
Cary_set: -> {find_a_dup_using_set(arr)},
Cary_diff: -> {find_a_dup_using_difference(arr)}
)
end
No incluí la respuesta de @ JjP porque solo se debe devolver un duplicado, y cuando se modifica su respuesta para hacerlo, es lo mismo que la respuesta anterior de @ Naveed. Tampoco incluí la respuesta de @ Marin, que, aunque se publicó antes de la respuesta de @ Naveed, devolvió todos los duplicados en lugar de solo uno (un punto menor, pero no hay ningún punto para evaluar ambos, ya que son idénticos cuando devuelven solo un duplicado).
También modifiqué otras respuestas que devolvieron todos los duplicados para devolver solo el primero encontrado, pero que esencialmente no debería tener ningún efecto en el rendimiento, ya que calcularon todos los duplicados antes de seleccionar uno.
Los resultados para cada punto de referencia se enumeran del más rápido al más lento:
Primero suponga que la matriz contiene 100 elementos:
benchmark(100, 0)
0 duplicates
Running each test 64 times. Test will take about 2 seconds.
Cary_set is similar to Cary_diff
Cary_diff is similar to Ryan
Ryan is similar to Sergio
Sergio is faster than Chris by 4x ± 1.0
Chris is faster than Naveed by 2x ± 1.0
benchmark(100, 1)
1 duplicates
Running each test 128 times. Test will take about 2 seconds.
Cary_set is similar to Cary_diff
Cary_diff is faster than Ryan by 2x ± 1.0
Ryan is similar to Sergio
Sergio is faster than Chris by 2x ± 1.0
Chris is faster than Naveed by 2x ± 1.0
benchmark(100, 10)
10 duplicates
Running each test 1024 times. Test will take about 3 seconds.
Chris is faster than Naveed by 2x ± 1.0
Naveed is faster than Cary_diff by 2x ± 1.0 (results differ: AAC vs AAF)
Cary_diff is similar to Cary_set
Cary_set is faster than Sergio by 3x ± 1.0 (results differ: AAF vs AAC)
Sergio is similar to Ryan
Ahora considere una matriz con 10,000 elementos:
benchmark(10000, 0)
0 duplicates
Running each test once. Test will take about 4 minutes.
Ryan is similar to Sergio
Sergio is similar to Cary_set
Cary_set is similar to Cary_diff
Cary_diff is faster than Chris by 400x ± 100.0
Chris is faster than Naveed by 3x ± 0.1
benchmark(10000, 1)
1 duplicates
Running each test once. Test will take about 1 second.
Cary_set is similar to Cary_diff
Cary_diff is similar to Sergio
Sergio is similar to Ryan
Ryan is faster than Chris by 2x ± 1.0
Chris is faster than Naveed by 2x ± 1.0
benchmark(10000, 10)
10 duplicates
Running each test once. Test will take about 11 seconds.
Cary_set is similar to Cary_diff
Cary_diff is faster than Sergio by 3x ± 1.0 (results differ: AAE vs AAA)
Sergio is similar to Ryan
Ryan is faster than Chris by 20x ± 10.0
Chris is faster than Naveed by 3x ± 1.0
benchmark(10000, 100)
100 duplicates
Cary_set is similar to Cary_diff
Cary_diff is faster than Sergio by 11x ± 10.0 (results differ: ADG vs ACL)
Sergio is similar to Ryan
Ryan is similar to Chris
Chris is faster than Naveed by 3x ± 1.0
Tenga en cuenta que find_a_dup_using_difference(arr)
sería mucho más eficiente siArray#difference
se implementara en C, que sería el caso si se agregara al núcleo de Ruby.
Conclusión
Muchas de las respuestas son razonables, pero usar un Set es la mejor opción . Es más rápido en los casos de dureza media, la unión más rápida en los casos más difíciles y solo computacionalmente triviales, cuando su elección no importará de todos modos, puede ser vencido.
El único caso muy especial en el que podría elegir la solución de Chris sería si desea utilizar el método para desduplicar por separado miles de matrices pequeñas y espera encontrar un duplicado que generalmente contiene menos de 10 elementos. Esto será un poco más rápido ya que evita la pequeña sobrecarga adicional de crear el Conjunto.
arr == arr.uniq
sería una manera fácil y elegante de verificar siarr
tiene duplicados, sin embargo, no proporciona cuáles fueron duplicados.