Oto dwa inne sposoby na znalezienie duplikatu.
Użyj zestawu
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"
Użyj selectzamiast, findaby zwrócić tablicę wszystkich duplikatów.
Posługiwać się 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"
Upuść, .firstaby zwrócić tablicę wszystkich duplikatów.
Obie metody zwracają się, niljeśli nie ma duplikatów.
I zaproponował, byArray#difference zostać dodany do rdzenia Ruby. Więcej informacji znajduje się w mojej odpowiedzi tutaj .
Reper
Porównajmy sugerowane metody. Najpierw potrzebujemy tablicy do testowania:
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
oraz metodę uruchamiania testów porównawczych dla różnych tablic testowych:
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
Nie załączyłem odpowiedzi @ JjP, ponieważ ma zostać zwrócony tylko jeden duplikat, a kiedy jego / jej odpowiedź jest tak zmodyfikowana, jest to taka sama, jak wcześniejsza odpowiedź @ Naveed. Nie zamieściłem również odpowiedzi @ Marin, która, mimo że opublikowana przed odpowiedzią @ Naveed, zwróciła wszystkie duplikaty, a nie tylko jeden (drobna uwaga, ale nie ma sensu oceniać obu, ponieważ są identyczne, gdy zwracają tylko jeden duplikat).
Zmodyfikowałem również inne odpowiedzi, które zwracały wszystkie duplikaty, aby zwracały tylko pierwszą znalezioną, ale to w zasadzie nie powinno mieć wpływu na wydajność, ponieważ obliczali wszystkie duplikaty przed wybraniem jednego.
Wyniki dla każdego testu porównawczego są wymienione od najszybszego do najwolniejszego:
Najpierw załóżmy, że tablica zawiera 100 elementów:
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
Rozważmy teraz tablicę zawierającą 10000 elementów:
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
Zauważ, że find_a_dup_using_difference(arr)byłoby to znacznie bardziej wydajne, gdyby Array#differencezostało zaimplementowane w C, co miałoby miejsce, gdyby zostało dodane do rdzenia Ruby.
Wniosek
Wiele odpowiedzi jest rozsądnych, ale użycie zestawu jest zdecydowanie najlepszym wyborem . Jest najszybszy w przypadkach średnio-twardych, łączony najszybciej w najtrudniejszych i tylko w przypadkach obliczeniowo błahych - gdy Twój wybór i tak nie ma znaczenia - można go pokonać.
Jedynym bardzo szczególnym przypadkiem, w którym możesz wybrać rozwiązanie Chrisa, jest to, że chcesz użyć metody do oddzielnego zduplikowania tysięcy małych tablic i spodziewasz się znaleźć duplikat zwykle mniej niż 10 elementów. Będzie to nieco szybsze ponieważ pozwala uniknąć małego dodatkowego obciążenia związanego z tworzeniem Zestawu.
arr == arr.uniqbyłby prostym i eleganckim sposobem sprawdzenia, czyarrma duplikaty, jednak nie dostarcza informacji, które zostały zduplikowane.