arr
est un tableau de chaînes, par exemple: ["hello", "world", "stack", "overflow", "hello", "again"]
.
Quel serait le moyen facile et élégant de vérifier si arr
a des doublons, et si oui, renvoyez l’un d’eux (peu importe lequel).
Exemples:
["A", "B", "C", "B", "A"] # => "A" or "B"
["A", "B", "C"] # => nil
a = ["A", "B", "C", "B", "A"]
a.detect{ |e| a.count(e) > 1 }
Je sais que ce n'est pas une réponse très élégante, mais j'adore ça. C'est beau un code de ligne. Et fonctionne parfaitement bien, sauf si vous devez traiter d’énormes quantités de données.
Vous cherchez une solution plus rapide? Voici!
def find_one_using_hash_map(array)
map = {}
dup = nil
array.each do |v|
map[v] = (map[v] || 0 ) + 1
if map[v] > 1
dup = v
break
end
end
return dup
end
Linéaire, O(n), mais il faut maintenant gérer plusieurs lignes de crédit, avoir besoin de scénarios de test, etc.
Si vous avez besoin d'une solution encore plus rapide, essayez peut-être C à la place :)
Et voici les gits qui comparent différentes solutions: https://Gist.github.com/naveed-ahmad/8f0b926ffccf5fbd206a1cc58ce9743e
Vous pouvez le faire de plusieurs manières, la première option étant la plus rapide:
ary = ["A", "B", "C", "B", "A"]
ary.group_by{ |e| e }.select { |k, v| v.size > 1 }.map(&:first)
ary.sort.chunk{ |e| e }.select { |e, chunk| chunk.size > 1 }.map(&:first)
Et une option O (N ^ 2) (c'est-à-dire moins efficace):
ary.select{ |e| ary.count(e) > 1 }.uniq
Trouvez simplement la première instance où l'indice de l'objet (en partant de la gauche) n'est pas égal à l'indice de l'objet (en partant de la droite).
arr.detect {|e| arr.rindex(e) != arr.index(e) }
S'il n'y a pas de doublons, la valeur de retour sera nil.
Je pense que c'est la solution la plus rapide publiée dans le fil jusqu'à présent, car elle ne repose pas sur la création d'objets supplémentaires, et #index
et #rindex
sont implémentés en C. La durée d'exécution est N ^ 2 et donc plus lente que celle de Sergio, mais la durée du mur pourrait être beaucoup plus rapide en raison du fait que les parties "lentes" fonctionnent en C.
detect
ne trouve qu'un seul doublon. find_all
les trouvera tous:
a = ["A", "B", "C", "B", "A"]
a.find_all { |e| a.count(e) > 1 }
Voici deux autres moyens de trouver un duplicata.
Utiliser 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.
Utilisez 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"
Déposez .first
pour renvoyer un tableau de tous les doublons.
Les deux méthodes renvoient nil
s'il n'y a pas de doublons.
I propose que Array#difference
soit ajouté au noyau Ruby. Plus d'informations sont dans ma réponse ici .
Benchmark
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 tests de performances pour différents tableaux de tests:
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 renvoyé. Lorsque sa réponse est modifiée, il est identique à la réponse précédente de @ Naveed. Je n'ai pas non plus inclus la réponse de @ Marin, qui, bien que postée avant la réponse de @ Naveed, a renvoyé tous les doublons plutôt qu'un seul (un point mineur mais inutile d'évaluer les deux, car ils sont identiques lorsqu'ils renvoient un seul duplicata).
J'ai également modifié d'autres réponses qui renvoyaient tous les doublons pour ne renvoyer que le premier trouvé, mais cela ne devrait essentiellement avoir aucun effet sur les performances, car ils calculaient tous les doublons avant d'en sélectionner un.
Les résultats pour chaque référence sont listé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 10 000 é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 find_a_dup_using_difference(arr)
serait beaucoup plus efficace si Array#difference
était implémenté en C, ce qui serait le cas si elle était ajoutée au Ruby core.
Conclusion
Beaucoup de réponses sont raisonnables, mais utiliser un ensemble est clairement le meilleur choix . Il est le plus rapide dans les cas moyennement difficiles, le plus rapide dans les cas les plus difficiles et seulement dans des cas triviaux sur le plan du calcul - quand votre choix n'aura aucune importance - peut-il ê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 attendre à trouver un duplicata généralement inférieur à 10 éléments. Ce sera un peu plus rapide. car cela évite le léger surcoût supplémentaire de la création de l'ensemble.
Hélas, la plupart des réponses sont O(n^2)
.
Voici une solution O(n)
,
a = %w{the quick brown fox jumps over the lazy dog}
h = Hash.new(0)
a.find { |each| (h[each] += 1) == 2 } # => 'the"
Quelle est la complexité de cela?
O(n)
et s'arrête au premier matchO(n)
memory, mais seulement la quantité minimaleMaintenant, en fonction de la fréquence des doublons dans votre tableau, ces temps d'exécution pourraient en réalité devenir encore meilleurs. Par exemple, si le tableau de taille O(n)
a été échantillonné à partir d'une population de k << n
éléments différents, seule la complexité à la fois pour l'exécution et l'espace devient O(k)
, mais il est plus probable que l'affiche d'origine valide l'entrée et souhaite s'assurer que il n'y a pas de doublons. Dans ce cas, la complexité d'exécution et de mémoire est O(n)
, car nous nous attendons à ce que les éléments n'aient aucune répétition pour la majorité des entrées.
Les objets Ruby Array ont une excellente méthode, select
.
select {|item| block } → new_ary
select → an_enumerator
La première forme est ce qui vous intéresse ici. Il vous permet de sélectionner des objets qui passent un test.
Les objets Ruby Array ont une autre méthode, count
.
count → int
count(obj) → int
count { |item| block } → int
Dans ce cas, vous êtes intéressé par les doublons (objets qui apparaissent plusieurs fois dans le tableau). Le test approprié est a.count(obj) > 1
.
Si a = ["A", "B", "C", "B", "A"]
, alors
a.select{|item| a.count(item) > 1}.uniq
=> ["A", "B"]
Vous déclarez que vous ne voulez que n objet. Alors choisissez-en un.
find_all () renvoie une array
contenant tous les éléments de enum
pour lesquels block
n'est pas false
.
Pour obtenir des éléments duplicate
>> arr = ["A", "B", "C", "B", "A"]
>> arr.find_all { |x| arr.count(x) > 1 }
=> ["A", "B", "B", "A"]
Ou dupliquer les éléments uniq
>> arr.find_all { |x| arr.count(x) > 1 }.uniq
=> ["A", "B"]
Quelque chose comme ça va marcher
arr = ["A", "B", "C", "B", "A"]
arr.inject(Hash.new(0)) { |h,e| h[e] += 1; h }.
select { |k,v| v > 1 }.
collect { |x| x.first }
Autrement dit, mettez toutes les valeurs dans un hachage où clé est l'élément de tableau et la valeur, le nombre d'occurrences. Sélectionnez ensuite tous les éléments qui apparaissent plusieurs fois. Facile.
Je sais que ce fil concerne spécifiquement Ruby, mais j'ai atterri ici en cherchant comment procéder dans le contexte de Ruby sur Rails avec ActiveRecord et je pensais que je le ferais. partager ma solution aussi.
class ActiveRecordClass < ActiveRecord::Base
#has two columns, a primary key (id) and an email_address (string)
end
ActiveRecordClass.group(:email_address).having("count(*) > 1").count.keys
Ce qui précède renvoie un tableau de toutes les adresses e-mail dupliquées dans la table de base de données de cet exemple (qui dans Rails serait "active_record_classes").
a = ["A", "B", "C", "B", "A"]
a.each_with_object(Hash.new(0)) {|i,hash| hash[i] += 1}.select{|_, count| count > 1}.keys
Ceci est une procédure O(n)
.
Sinon, vous pouvez faire l'une des lignes suivantes. Aussi O(n) mais une seule itération
a.each_with_object(Hash.new(0).merge dup: []){|x,h| h[:dup] << x if (h[x] += 1) == 2}[:dup]
a.inject(Hash.new(0).merge dup: []){|h,x| h[:dup] << x if (h[x] += 1) == 2;h}[:dup]
Voici mon point de vue sur un grand ensemble de données, tel qu'un tableau dBase hérité pour rechercher les doublons.
# Assuming ps is an array of 20000 part numbers & we want to find duplicates
# actually had to it recently.
# having a result hash with part number and number of times part is
# duplicated is much more convenient in the real world application
# Takes about 6 seconds to run on my data set
# - not too bad for an export script handling 20000 parts
h = {};
# or for readability
h = {} # result hash
ps.select{ |e|
ct = ps.count(e)
h[e] = ct if ct > 1
}; nil # so that the huge result of select doesn't print in the console
Si vous comparez deux tableaux différents (au lieu d'un seul contre lui-même), un moyen très rapide consiste à utiliser l'opérateur d'intersection &
fourni par classe Ruby's Array .
# Given
a = ['a', 'b', 'c', 'd']
b = ['e', 'f', 'c', 'd']
# Then this...
a & b # => ['c', 'd']
each_with_object
est votre ami!
input = [:bla,:blubb,:bleh,:bla,:bleh,:bla,:blubb,:brrr]
# to get the counts of the elements in the array:
> input.each_with_object({}){|x,h| h[x] ||= 0; h[x] += 1}
=> {:bla=>3, :blubb=>2, :bleh=>2, :brrr=>1}
# to get only the counts of the non-unique elements in the array:
> input.each_with_object({}){|x,h| h[x] ||= 0; h[x] += 1}.reject{|k,v| v < 2}
=> {:bla=>3, :blubb=>2, :bleh=>2}
r = [1, 2, 3, 5, 1, 2, 3, 1, 2, 1]
r.group_by(&:itself).map { |k, v| v.size > 1 ? [k] + [v.size] : nil }.compact.sort_by(&:last).map(&:first)
J'avais besoin de savoir combien il y avait de doublons et ce qu'ils étaient. J'ai donc écrit une construction de fonction à partir de ce que Naveed avait posté plus tôt:
def print_duplicates(array)
puts "Array count: #{array.count}"
map = {}
total_dups = 0
array.each do |v|
map[v] = (map[v] || 0 ) + 1
end
map.each do |k, v|
if v != 1
puts "#{k} appears #{v} times"
total_dups += 1
end
end
puts "Total items that are duplicated: #{total_dups}"
end
a = ["A", "B", "C", "B", "A"]
b = a.select {|e| a.count(e) > 1}.uniq
c = a - b
d = b + c
Résultats
d
=> ["A", "B", "C"]