Вот еще два способа найти дубликат.
Используйте набор
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"
Отбросьте, .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. Я также не включил ответ @ Marin, который, хотя и был опубликован до ответа @ 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
есть ли дубликаты, однако, он не предоставляет, которые были дублированы.