Ось ще два способи пошуку дубліката.
Використовуйте набір
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"
Використовуйте select
замість, find
щоб повернути масив усіх дублікатів.
Використовуйте 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"
Drop, .first
щоб повернути масив усіх дублікатів.
Обидва способи повертаються, nil
якщо немає дублікатів.
Я запропонувавArray#difference
додати його до ядра Ruby. Більше інформації - у моїй відповіді тут .
Орієнтир
Порівняємо запропоновані методи. Спочатку нам потрібен масив для тестування:
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
і метод запуску орієнтирів для різних тестових масивів:
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
Я не включив відповідь @ JjP, тому що потрібно повернути лише один дублікат, і коли його / її відповідь буде змінено, щоб це зробити, це те саме, що і попередня відповідь @ Naveed. Я також не включив відповідь @ Маріна, яка, опублікована перед відповіддю @ Naveed, повертала всі дублікати, а не лише один (другорядний пункт, але немає жодної точки, що оцінює обидва, оскільки вони ідентичні, коли повертаються лише один дублікат).
Я також змінив інші відповіді, які повертали всі дублікати, щоб повернути лише перший знайдений, але це не мало по суті ніякого впливу на продуктивність, оскільки вони обчислювали всі дублікати перед вибором одного.
Результати для кожного еталону перераховані від найшвидшого до найповільнішого:
Спочатку припустимо, що масив містить 100 елементів:
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
Тепер розглянемо масив із 10000 елементів:
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
Зауважте, що find_a_dup_using_difference(arr)
було б набагато ефективніше, якби вони Array#difference
були реалізовані в C, що було б у випадку, якщо б його було додано до ядра Ruby.
Висновок
Багато відповідей є розумними, але використання набору - це найкращий вибір . Це найшвидше у випадках із середньою жорсткістю, найшвидший суглоб у найважчих і лише у обчислювально тривіальних випадках - коли ваш вибір все одно не має значення - чи можна його побити.
Єдиним особливим випадком, коли ви можете вибрати рішення Кріса, буде, якщо ви хочете скористатися методом для окремого видалення копій тисяч малих масивів і очікуєте, що вони знайдуть дублікат, як правило, менше 10 елементів. Це буде трохи швидше оскільки це дозволяє уникнути невеликих додаткових витрат на створення набору.
arr == arr.uniq
Це був би простий і елегантний спосіб перевірити наявністьarr
дублікатів, однак він не передбачає, які були дублюються.