Aqui estão mais duas maneiras de encontrar uma duplicata.
Use um 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"
Use select
no lugar de find
para retornar uma matriz de todas as duplicatas.
Usar 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"
Solte .first
para retornar uma matriz de todas as duplicatas.
Ambos os métodos retornam nil
se não houver duplicatas.
Eu propus queArray#difference
fosse adicionado ao núcleo do Ruby. Mais informações estão na minha resposta aqui .
Referência
Vamos comparar os métodos sugeridos. Primeiro, precisamos de uma matriz para testar:
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
e um método para executar os benchmarks para diferentes matrizes de teste:
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
Não incluí a resposta de @ JjP porque apenas uma duplicata deve ser retornada e, quando sua resposta é modificada para fazer isso, é igual à resposta anterior de @ Naveed. Também não incluí a resposta de @ Marin, que, embora postada antes da resposta de @ Naveed, retornava todas as duplicatas em vez de apenas uma (um ponto menor, mas não faz sentido avaliar as duas, pois são idênticas quando retornam apenas uma duplicata).
Também modifiquei outras respostas que retornaram todas as duplicatas para retornar apenas a primeira encontrada, mas que não deveriam ter nenhum efeito sobre o desempenho, pois calculavam todas as duplicatas antes de selecionar uma.
Os resultados de cada benchmark estão listados do mais rápido ao mais lento:
Primeiro, suponha que a matriz contenha 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
Agora considere uma matriz com 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
Observe que find_a_dup_using_difference(arr)
seria muito mais eficiente se Array#difference
fosse implementado em C, o que seria o caso se fosse adicionado ao núcleo do Ruby.
Conclusão
Muitas das respostas são razoáveis, mas usar um Conjunto é a melhor opção . É o mais rápido nos casos médios, o mais rápido nos casos mais difíceis e apenas nos casos computacionalmente triviais - quando sua escolha não importa de qualquer maneira - pode ser derrotado.
O único caso muito especial em que você pode escolher a solução de Chris seria se você deseja usar o método para desduplicar separadamente milhares de arrays pequenos e esperar encontrar um duplicado normalmente com menos de 10 itens. Isso será um pouco mais rápido pois evita a pequena sobrecarga adicional de criar o conjunto.
arr == arr.uniq
seria uma maneira fácil e elegante de verificar searr
há duplicatas, no entanto, não fornece quais foram duplicadas.