Vérifiez si deux tableaux ont le même contenu (dans n'importe quel ordre)


90

J'utilise Ruby 1.8.6 avec Rails 1.2.3 et je dois déterminer si deux tableaux ont les mêmes éléments, qu'ils soient ou non dans le même ordre. L'un des tableaux est garanti de ne pas contenir de doublons (l'autre pourrait, auquel cas la réponse est non).

Ma première pensée a été

require 'set'
a.to_set == b.to_set

mais je me demandais s'il y avait une manière plus efficace ou idiomatique de le faire.



Essayez array.should = ~ another_array check stackoverflow.com/questions/2978922/…
Athena

Vous auriez pu éviter beaucoup de confusion en: 1) indiquant si les éléments des tableaux sont nécessairement triables; et 2) fournissez un exemple simple pour clarifier ce que vous entendez par «si deux tableaux ont les mêmes éléments» (par exemple, ont-ils [1,2]et [2,1,1]ont les mêmes éléments?)
Cary Swoveland

Ruby 2.6 a introduit differencequi offre une solution à la fois très rapide et très lisible. Plus d'infos ici.
SRack

Réponses:


140

Cela ne nécessite pas de conversion pour définir:

a.sort == b.sort

Pas de conversion? Qu'est-ce .uniq.sortdonc? En plus uniqest similaire à en to_setinterne plus supplémentaire.to_a.sort
Victor Moroz

Accepter cela car c'est plus proche de ce que j'ai fini par utiliser, mais sans le uniqs. En fait, j'ai fini par créer l'un des tableaux avec Range#to_a, donc je n'avais que sortl'autre.
Taymon

11
Cela ne fonctionnera pas si le tableau contient des éléments qui ne peuvent pas être simplement triés (par exemple un tableau de hachages). La solution de sahil dhankhar semble être une solution plus générale.
brad

40

pour deux tableaux A et B: A et B ont le même contenu si: (A-B).blank? and (B-A).blank?

ou vous pouvez simplement vérifier: ((A-B) + (B-A)).blank?

Également comme suggéré par @ cort3z, cette solution fonctionne également pour les tableaux polymorphes ie

 A = [1 , "string", [1,2,3]]
 B = [[1,2,3] , "string", 1]
 (A-B).blank? and (B-A).blank? => true
 # while A.uniq.sort == B.uniq.sort will throw error `ArgumentError: comparison of Fixnum with String failed` 

::::::::::: ÉDITER :::::::::::::

Comme suggéré dans les commentaires, la solution ci-dessus échoue pour les doublons.Bien que, selon la question, cela ne soit même pas nécessaire puisque le demandeur n'est pas intéressé par les doublons (il convertit ses tableaux en ensemble avant de vérifier et cela masque les doublons et même si vous regardez la réponse acceptée, il utilise un opérateur .uniq avant de vérifier et qui masque aussi les doublons.). Mais toujours si les doublons vous intéressent, le simple fait d'ajouter une vérification du nombre corrigera la même chose (selon la question, un seul tableau peut contenir des doublons). La solution finale sera donc: A.size == B.size and ((A-B) + (B-A)).blank?


Cela échouera si l'un des tableaux contient des doublons. Par exemple, si A=[1]et B=[1,1], à la fois (A-B)et (B-A)retournera vide. Consultez la documentation sur les baies .
jtpereyda

@dafrazzman est totalement d'accord avec vous. J'ai modifié ma réponse pour intégrer vos commentaires.Mais si vous regardez de près la question (ou la réponse acceptée), le demandeur utilise: a.to_set == b.to_set et la réponse acceptée utilise a.uniq.sort == b.uniq.sortet les deux donnent exactement le même résultat que ((A-B) + (B-A)).blank?pour A = [1] et B = [1,1] d'accord? Puisqu'il demandait simplement une amélioration par rapport à sa solution d'origine, ma solution d'origine fonctionne toujours :). se mettre d'accord?
Sahil Dhankhar

1
Cette solution est assez sympa puisqu'elle gère des objets de plusieurs types. Dites que vous avez A = [123, "test", [], some_object, nil]et B = A#because I am lazy, puis A.uniq.sortgénérera une erreur (la comparaison de la chaîne et du tableau a échoué).
Automatico

Serait-ce alors O (n) puisque cela dépend de la taille du tableau? (linéaire)
user3007294

1
Cela ne fonctionnerait pas si les tableaux ont la même taille mais les éléments répétés ne sont pas les mêmes. Par exemple A = [1, 1, 2]etB = [1, 2, 2]
Boudi

23

Comparaisons de vitesse

require 'benchmark/ips'
require 'set'

a = [1, 2, 3, 4, 5, 6]
b = [1, 2, 3, 4, 5, 6]

Benchmark.ips do |x|
  x.report('sort')   { a.sort == b.sort }  
  x.report('sort!')  { a.sort! == b.sort! }  
  x.report('to_set') { a.to_set == b.to_set }  
  x.report('minus')  { ((a - b) + (b - a)).empty? }  
end  

Warming up --------------------------------------
            sort    88.338k i/100ms
           sort!   118.207k i/100ms
          to_set    19.339k i/100ms
           minus    67.971k i/100ms
Calculating -------------------------------------
            sort      1.062M  0.9%) i/s -      5.389M in   5.075109s
           sort!      1.542M  1.2%) i/s -      7.802M in   5.061364s
          to_set    200.302k  2.1%) i/s -      1.006M in   5.022793s
           minus    783.106k  1.5%) i/s -      3.942M in   5.035311s

btw ordre des elemetns n'affecte pas sortla vitesse de
Morozov

M'ai surpris ... Je m'attendais à ce que la comparaison par ensemble surpasse toutes les autres en raison de la complexité temporelle O (n) de la recherche d'ensembles. Ainsi, tout tri bien implémenté nécessiterait O (n logn). Alors que la conversion en ensembles et la recherche de valeurs le feraient globalement en un temps O (n).
Oleg Afanasyev

1
Je m'attendrais to_setà commencer à surpasser avec des tableaux suffisamment grands où O (n logn) commencerait à avoir plus d'importance que l'effort requis pour convertir un tableau en ensemble
Andrius Chamentauskas

1
C'est utile, mais pas vraiment une réponse en soi? Peut-être mieux l'ajouter à une solution existante?
SRack

18

Ruby 2.6+

Ruby introduit differencedans 2.6.

Cela donne une solution très rapide et très lisible ici, comme suit:

a = [1, 2, 3, 4, 5, 6]
b = [1, 2, 3, 4, 5, 6]

a.difference(b).any?
# => false
a.difference(b.reverse).any?
# => false

a = [1, 2, 3, 4, 5, 6]
b = [1, 2, 3]
a.difference(b).any?
# => true

Exécution des benchmarks:

a = Array.new(1000) { rand(100) }
b = Array.new(1000) { rand(100) }

Benchmark.ips do |x|
  x.report('sort')   { a.sort == b.sort }  
  x.report('sort!')  { a.sort! == b.sort! }  
  x.report('to_set') { a.to_set == b.to_set }  
  x.report('minus')  { ((a - b) + (b - a)).empty? }  
  x.report('difference') { a.difference(b).any? }
end

      sort     13.908k  2.6%) i/s -     69.513k in   5.001443s
     sort!     14.656k  3.0%) i/s -     73.736k in   5.035744s
    to_set     5.125k   2.9%) i/s -     26.023k in   5.082083s
     minus     16.398k  2.2%) i/s -     83.181k in   5.074938s
difference     27.839k  5.2%) i/s -    141.048k in   5.080706s

J'espère que cela aide quelqu'un!


2
la différence se brise dans ce cas a = [1, 2, 3] b = [1, 2, 3, 4, 5] a. différence (b). => false
error-404


8

Si vous prévoyez [:a, :b] != [:a, :a, :b] to_setne fonctionne pas. Vous pouvez utiliser la fréquence à la place:

class Array
  def frequency
    p = Hash.new(0)
    each{ |v| p[v] += 1 }
    p
  end
end

[:a, :b].frequency == [:a, :a, :b].frequency #=> false
[:a, :b].frequency == [:b, :a].frequency #=> true

pourquoi pas juste a.sort == b.sorts'il se soucie de la fréquence?
fl00r

4
@ fl00r Et si les éléments ne sont pas comparables? ["", :b].frequency == [:b, ""].frequency #=> true
Victor Moroz

2
aussi vous pouvez faire quelque chose de fonctionnel commea.group_by{|i| i} == b.group_by{|i| i}
fl00r

7

Si vous savez que les tableaux sont de longueur égale et qu'aucun des tableaux ne contient de doublons, cela fonctionne également:

( array1 & array2 ) == array1

Explication:& dans ce cas, l' opérateur renvoie une copie de a1 sans tous les éléments non trouvés dans a2, qui est identique à l'original a1 si les deux tableaux ont le même contenu sans doublons.

Analyse: Étant donné que l'ordre est inchangé, je suppose que cela est implémenté comme une double itération de manière cohérente O(n*n), notamment pire pour les grands tableaux que a1.sort == a2.sortce qui devrait fonctionner avec le pire des cas O(n*logn).


2
Ne fonctionne pas toujours: a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2retourne [2,1,3]pour moi qui n'est pas égal àa1
Kalyan Raghu

@Kaylan, tu ne veux pas dire que ça ne marche que quand a1==a2? Cela peut fonctionner si array1du côté droit de l'égalité est remplacé par array2, mais je doute que l'ordre des éléments renvoyés par &soit garanti.
Cary Swoveland

1
@KalyanRaghu &est un opérateur d'intersection défini pour les tableaux, &&est logique ET - ils sont très différents!
Kimball

2

Une approche consiste à parcourir le tableau sans doublons

# assume array a has no duplicates and you want to compare to b
!a.map { |n| b.include?(n) }.include?(false)

Cela renvoie un tableau de vrais. Si un faux apparaît, alors l'extérieur include?retournera vrai. Ainsi, vous devez inverser le tout pour déterminer si c'est une correspondance.


@Victor Moroz, vous avez raison, et un compte de fréquence serait simplement O (n).
Ron

2

combinaison &et sizepeut être rapide aussi.

require 'benchmark/ips'
require 'set'

Benchmark.ips do |x|
  x.report('sort')   { a.sort == b.sort }  
  x.report('sort!')  { a.sort! == b.sort! }  
  x.report('to_set') { a.to_set == b.to_set }  
  x.report('minus')  { ((a - b) + (b - a)).empty? }
  x.report('&.size') { a.size == b.size && (a & b).size == a.size }  
end  

Calculating -------------------------------------
                sort    896.094k 11.4%) i/s -      4.458M in   5.056163s
               sort!      1.237M  4.5%) i/s -      6.261M in   5.071796s
              to_set    224.564k  6.3%) i/s -      1.132M in   5.064753s
               minus      2.230M  7.0%) i/s -     11.171M in   5.038655s
              &.size      2.829M  5.4%) i/s -     14.125M in   5.010414s
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.