web-dev-qa-db-fra.com

Ruby: Comment trouver et retourner une valeur en double dans un tableau?

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
152
Misha Moroshko
a = ["A", "B", "C", "B", "A"]
a.detect{ |e| a.count(e) > 1 }

MISE À JOUR

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

229
Naveed

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
203
Ryan LeCompte

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.

43
Chris Heald

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 }
27
JjP

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.

19
Cary Swoveland

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?

  • S'exécute dans O(n) et s'arrête au premier match
  • Utilise O(n) memory, mais seulement la quantité minimale

Maintenant, 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.

15
akuhn

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.

14
Martin Velez

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"] 
7
Rokibul Hasan

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.

7
Sergio Tulentsev

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").

7
danielricecodes
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]
5
benzhang

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
2
konung

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']
1
IAmNaN

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}
1
Tilo
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)
1
Dorian

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
0
muneebahmad
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"]
0
Amrit Dhungana