Voici deux autres façons de trouver un doublon.
Utilisez un ensemble
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"
Utilisez select
à la place de find
pour renvoyer un tableau de tous les doublons.
Utilisation 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
pour renvoyer un tableau de tous les doublons.
Les deux méthodes retournent nil
s'il n'y a pas de doublons.
J'ai proposé que celaArray#difference
soit ajouté au noyau Ruby. Plus d'informations sont dans ma réponse ici .
Référence
Comparons les méthodes suggérées. Tout d'abord, nous avons besoin d'un tableau pour tester:
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
et une méthode pour exécuter les benchmarks pour différents tableaux de test:
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
Je n'ai pas inclus la réponse de @ JjP car un seul duplicata doit être retourné, et lorsque sa réponse est modifiée pour cela, c'est la même chose que la réponse précédente de @ Naveed. Je n'ai pas non plus inclus la réponse de @ Marin, qui, bien que publiée avant la réponse de @ Naveed, a renvoyé tous les doublons plutôt qu'un seul (un point mineur mais il est inutile d'évaluer les deux, car ils sont identiques lorsqu'ils ne renvoient qu'un seul double).
J'ai également modifié d'autres réponses qui renvoyaient tous les doublons pour ne renvoyer que la première trouvée, mais cela ne devrait essentiellement avoir aucun effet sur les performances, car ils ont calculé tous les doublons avant d'en sélectionner un.
Les résultats de chaque benchmark sont répertoriés du plus rapide au plus lent:
Supposons d'abord que le tableau contienne 100 éléments:
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
Considérons maintenant un tableau avec 10000 éléments:
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
Notez que ce find_a_dup_using_difference(arr)
serait beaucoup plus efficace s'il Array#difference
était implémenté en C, ce qui serait le cas s'il était ajouté au noyau Ruby.
Conclusion
Beaucoup de réponses sont raisonnables, mais l' utilisation d'un ensemble est clairement le meilleur choix . Il est le plus rapide dans les cas moyennement durs, le joint le plus rapide dans les cas les plus difficiles et seulement dans les cas informatiques triviaux - lorsque votre choix n'a pas d'importance de toute façon - il peut être battu.
Le cas très particulier dans lequel vous pourriez choisir la solution de Chris serait si vous souhaitez utiliser la méthode pour dédupliquer séparément des milliers de petits tableaux et vous attendez à trouver un doublon généralement moins de 10 éléments. Ce sera un peu plus rapide car cela évite la petite surcharge supplémentaire liée à la création de l'ensemble.
arr == arr.uniq
serait un moyen simple et élégant de vérifier s'il yarr
a des doublons, mais il ne fournit pas ceux qui ont été dupliqués.