Überprüfen Sie, ob zwei Arrays denselben Inhalt haben (in beliebiger Reihenfolge).


89

Ich verwende Ruby 1.8.6 mit Rails 1.2.3 und muss feststellen, ob zwei Arrays dieselben Elemente haben, unabhängig davon, ob sie in derselben Reihenfolge sind oder nicht. Eines der Arrays enthält garantiert keine Duplikate (das andere könnte, in diesem Fall lautet die Antwort nein).

Mein erster Gedanke war

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

aber ich habe mich gefragt, ob es einen effizienteren oder idiomatischeren Weg gibt, dies zu tun.



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

Sie hätten viel Verwirrung ersparen können, indem Sie: 1) angegeben haben, ob die Elemente der Arrays notwendigerweise sortierbar sind; und 2) ein einfaches Beispiel liefern, um zu verdeutlichen, was Sie unter "ob zwei Arrays dieselben Elemente haben" verstehen (z. B. dieselben Elemente haben [1,2]und [2,1,1]haben?)
Cary Swoveland,

Ruby 2.6 hat differenceeine Lösung eingeführt, die sowohl sehr schnell als auch gut lesbar ist. Mehr Infos hier.
SRack

Antworten:


138

Dies erfordert keine Konvertierung, um Folgendes festzulegen:

a.sort == b.sort

Keine Konvertierung? Was ist .uniq.sortdann? Außerdem uniqist ähnlich wie to_setintern plus zusätzlich.to_a.sort
Victor Moroz

Akzeptiere dies, da es dem am nächsten kommt, was ich letztendlich verwendet habe, allerdings ohne das uniqs. Eigentlich habe ich eines der Arrays mit erstellt Range#to_a, also musste ich nur sortdas andere.
Taymon

10
Dies funktioniert nicht, wenn das Array Elemente enthält, die nicht einfach sortiert werden können (z. B. ein Array von Hashes). Sahil Dhankhars Lösung scheint eine allgemeinere Lösung zu sein.
Brad

40

für zwei Arrays A und B: A und B haben den gleichen Inhalt, wenn: (A-B).blank? and (B-A).blank?

oder Sie können einfach nachsehen: ((A-B) + (B-A)).blank?

Auch wie von @ cort3z vorgeschlagen, funktioniert diese Lösung auch für polymorphe Arrays, d. H.

 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` 

::::::::::: BEARBEITEN ::::::::::::::

Wie in den Kommentaren vorgeschlagen, schlägt die obige Lösung für Duplikate fehl. Obwohl gemäß der Frage, die nicht einmal erforderlich ist, da der Fragesteller nicht an Duplikaten interessiert ist (er konvertiert seine Arrays vor dem Überprüfen in einen Satz und maskiert Duplikate und selbst wenn Sie sich diese ansehen Die akzeptierte Antwort verwendet er vor dem Überprüfen mit einem .uniq-Operator und maskiert auch Duplikate.). Wenn Sie dennoch an Duplikaten interessiert sind, wird dies durch Hinzufügen einer Überprüfung der Anzahl behoben (gemäß der Frage kann nur ein Array Duplikate enthalten). Die endgültige Lösung lautet also: A.size == B.size and ((A-B) + (B-A)).blank?


Dies schlägt fehl, wenn eines der Arrays Duplikate enthält. ZB wenn A=[1]und B=[1,1], beide (A-B)und (B-A)leer zurückgeben. Siehe Array-Dokumentation .
jtpereyda

@dafrazzman stimme dir vollkommen zu. Ich habe meine Antwort geändert, um Ihr Feedback einzubeziehen. Wenn Sie sich jedoch die Frage (oder die akzeptierte Antwort) genau ansehen, verwendet der Fragesteller: a.to_set == b.to_set und die akzeptierte Antwort verwendet a.uniq.sort == b.uniq.sortund beide liefern genau das gleiche Ergebnis wie ((A-B) + (B-A)).blank?für A = [1]. und B = [1,1] stimmen überein? Da er nur um eine Verbesserung gegenüber seiner ursprünglichen Lösung gebeten hat, funktioniert meine ursprüngliche Lösung immer noch :). zustimmen?
Sahil Dhankhar

1
Diese Lösung ist sehr schön, da sie Objekte verschiedener Typen verarbeitet. Angenommen, Sie haben A = [123, "test", [], some_object, nil]und B = A#because I am lazy, dann A.uniq.sortwird ein Fehler ausgegeben (Vergleich von Zeichenfolge und Array fehlgeschlagen).
Automatico

Wäre dies dann O (n), da es von der Arraygröße abhängt? (linear)
user3007294

1
Es würde nicht funktionieren, wenn die Arrays dieselbe Größe haben, aber die wiederholten Elemente nicht gleich sind. Zum Beispiel A = [1, 1, 2]undB = [1, 2, 2]
Boudi

23

Geschwindigkeitsvergleiche

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

Übrigens hat die Reihenfolge der Elemente keinen Einfluss auf sortdie Geschwindigkeit
Morozov

Überrascht mich ... Ich habe erwartet , dass der By -Set- Vergleich alle anderen übertrifft, da die Komplexität der Set-Lookup-O (n) -Zeit sehr hoch ist. Damit jede gut implementierte Sortierung O (n logn) erfordert. Während das Casting in Mengen und das Nachschlagen von Werten es insgesamt in O (n) Zeit schaffen würde.
Oleg Afanasyev

1
Ich würde erwarten to_set, eine Outperformance mit ausreichend großen Arrays zu erzielen, bei denen O (n logn) mehr zählt als der Aufwand, der erforderlich ist, um das Array in ein Set umzuwandeln
Andrius Chamentauskas,

1
Dies ist hilfreich, aber nicht wirklich eine Antwort an sich? Vielleicht besser zu einer bestehenden Lösung hinzufügen?
SRack

18

Ruby 2.6+

Ruby wird differencein 2.6 eingeführt.

Dies ergibt hier eine sehr schnelle, sehr lesbare Lösung wie folgt:

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

Ausführen der 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

Hoffe das hilft jemandem!


2
Differenz bricht in diesem Fall a = [1, 2, 3] b = [1, 2, 3, 4, 5] a.differenz (b) .jeder? => false
error-404


8

Wenn Sie erwarten, [:a, :b] != [:a, :a, :b] to_setfunktioniert nicht. Sie können stattdessen die Frequenz verwenden:

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

warum nicht nur, a.sort == b.sortwenn ihm die Frequenz am Herzen liegt?
fl00r

4
@ fl00r Was ist, wenn Artikel nicht vergleichbar sind? ["", :b].frequency == [:b, ""].frequency #=> true
Victor Moroz

2
Sie können auch etwas Funktionales tun alsa.group_by{|i| i} == b.group_by{|i| i}
fl00r

7

Wenn Sie wissen, dass die Arrays gleich lang sind und keines der Arrays Duplikate enthält, funktioniert dies auch:

( array1 & array2 ) == array1

Erläuterung:& In diesem Fall gibt der Operator eine Kopie von a1 ohne alle in a2 nicht gefundenen Elemente zurück. Dies entspricht dem Original a1, wenn beide Arrays denselben Inhalt ohne Duplikate haben.

Analyse: Da die Reihenfolge unverändert ist, wird dies vermutlich als Doppeliteration implementiert, die so konsistent ist O(n*n), insbesondere bei großen Arrays, a1.sort == a2.sortdie im schlimmsten Fall schlechter abschneiden sollten O(n*logn).


2
Funktioniert nicht immer: a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2kehrt [2,1,3]für mich zurück, was nicht gleich ista1
Kalyan Raghu

@ Kaylan, meinst du nicht, dass es nur funktioniert, wenn a1==a2? Es mag funktionieren, wenn array1auf der rechten Seite die Gleichheit durch ersetzt wird array2, aber ich bezweifle, dass die Reihenfolge der zurückgegebenen Elemente &garantiert ist.
Cary Swoveland

1
@KalyanRaghu &ist ein festgelegter Schnittpunktoperator für Arrays, &&ist logisch UND - sie sind sehr unterschiedlich!
Kimball

2

Ein Ansatz besteht darin, das Array ohne Duplikate zu durchlaufen

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

Dies gibt ein Array von Wahrheiten zurück. Wenn false angezeigt wird, gibt das äußere include?true zurück. Sie müssen also das Ganze umkehren, um festzustellen, ob es sich um eine Übereinstimmung handelt.


@ Victor Moroz, Sie haben Recht, und eine Frequenzzählung wäre einfach O (n).
Ron

2

kombinieren &und sizekann auch schnell sein.

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.