Sprawdź, czy dwie tablice mają tę samą zawartość (w dowolnej kolejności)

90

Używam Ruby 1.8.6 z Railsami 1.2.3 i muszę określić, czy dwie tablice mają te same elementy, niezależnie od tego, czy są w tej samej kolejności. Jedna z tablic na pewno nie zawiera duplikatów (druga może, w takim przypadku odpowiedź brzmi nie).

Moja pierwsza myśl była taka

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

ale zastanawiałem się, czy istnieje bardziej skuteczny lub idiomatyczny sposób na zrobienie tego.

Taymon
źródło
Wypróbuj array.should = ~ another_array sprawdź stackoverflow.com/questions/2978922/…
Athena
Można było zaoszczędzić wiele nieporozumień: 1) stwierdzając, czy elementy tablic są koniecznie sortowalne; i 2) zapewniają prosty przykład wyjaśnić, co masz na myśli przez „czy dwie tablice mają te same elementy” (na przykład zrobić [1,2]i [2,1,1]mają te same elementy)?
Cary Swoveland
Ruby 2.6 wprowadził differencerozwiązanie, które oferuje rozwiązanie zarówno bardzo szybkie, jak i bardzo czytelne. Więcej informacji tutaj.
SRack

Odpowiedzi:

140

Nie wymaga to konwersji, aby ustawić:

a.sort == b.sort
Mori
źródło
Brak konwersji? Co jest .uniq.sortwtedy? Poza tym uniqjest podobny do to_setwewnętrznego plus dodatkowy.to_a.sort
Victor Moroz
Akceptuję to, ponieważ jest to najbliższe temu, czego użyłem, choć bez uniqs. Właściwie skończyło się na utworzeniu jednej z tablic z Range#to_a, więc musiałem tylko do sortdrugiej.
Taymon
11
To nie zadziała, jeśli tablica zawiera elementy, których nie można po prostu posortować (np. Tablicę skrótów). Rozwiązanie Sahila dhankhara wydaje się być rozwiązaniem bardziej ogólnym.
brad
40

dla dwóch tablic A i B: A i B mają tę samą zawartość, jeśli: (A-B).blank? and (B-A).blank?

lub możesz po prostu sprawdzić: ((A-B) + (B-A)).blank?

Również, jak sugeruje @ cort3z, to rozwiązanie als0 działa dla tablic polimorficznych, tj

 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` 

::::::::::: EDYTOWAĆ :::::::::::::

Jak zasugerowano w komentarzach, powyższe rozwiązanie zawodzi dla duplikatów, chociaż zgodnie z pytaniem, które nawet nie jest wymagane, ponieważ pytający nie jest zainteresowany duplikatami (konwertuje swoje tablice na zestaw przed sprawdzeniem i maskuje duplikaty, nawet jeśli spojrzysz na zaakceptowana odpowiedź, przed sprawdzeniem używa operatora .uniq, co również maskuje duplikaty). Ale nadal, jeśli interesują Cię duplikaty, samo dodanie sprawdzenia liczby naprawi to samo (zgodnie z pytaniem tylko jedna tablica może zawierać duplikaty). Tak więc ostatecznym rozwiązaniem będzie: A.size == B.size and ((A-B) + (B-A)).blank?

Sahil Dhankhar
źródło
To się nie powiedzie, jeśli jedna z tablic zawiera duplikaty. Np. Jeśli A=[1]i B=[1,1], oba (A-B)i (B-A)zwróci wartość pustą. Zobacz dokumentację tablicy .
jtpereyda
@dafrazzman całkowicie się z tobą zgadzam. Zmodyfikowałem moją odpowiedź, aby uwzględnić Twoją opinię. Ale jeśli przyjrzysz się bliżej pytaniu (lub zaakceptowanej odpowiedzi), pytający używa: a.to_set == b.to_set a zaakceptowana odpowiedź używa a.uniq.sort == b.uniq.sorti obie dają dokładnie taki sam wynik jak ((A-B) + (B-A)).blank?dla A = [1] i B = [1,1] zgadzasz się? Ponieważ prosił tylko o ulepszenie swojego oryginalnego rozwiązania, moje oryginalne rozwiązanie nadal działa :). Zgodzić się?
Sahil Dhankhar
1
To rozwiązanie jest całkiem przyjemne, ponieważ obsługuje obiekty wielu typów. Załóżmy, że masz A = [123, "test", [], some_object, nil]i B = A#because I am lazy, a następnie A.uniq.sortwygeneruje błąd (porównanie z ciągiem i Array nie powiodło się).
Automatico
Czy to byłoby O (n), skoro zależy od rozmiaru tablicy? (liniowa)
user3007294
1
Nie zadziałałoby, gdyby tablice miały ten sam rozmiar, ale powtarzające się elementy nie są takie same. Na przykład A = [1, 1, 2]iB = [1, 2, 2]
Boudi
23

Porównanie prędkości

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
Morozov
źródło
btw kolejność elemetnów nie wpływa na sortprędkość
Morozov
Zaskoczyło mnie ... Spodziewałem się, że porównanie według zestawu będzie lepsze od wszystkich innych dzięki wyszukiwaniu zestawów O (n) złożoności czasowej. Tak więc każde dobrze zaimplementowane sortowanie wymagałoby O (n logn). Podczas gdy rzutowanie na zbiory i wyszukiwanie wartości ogólnie dałoby to w czasie O (n).
Oleg Afanasyev
1
Spodziewałbym się, że to_setzacznę osiągać lepsze wyniki z wystarczająco dużymi tablicami, w których O (n logn) zacznie mieć większe znaczenie niż wysiłek wymagany do konwersji tablicy na zestaw
Andrius Chamentauskas
1
Jest to pomocne, ale nie jest odpowiedzią samą w sobie? Może lepiej dodać to do istniejącego rozwiązania?
SRack
19

Ruby 2.6+

Ruby został wprowadzony differencew 2.6.

Daje to bardzo szybkie, bardzo czytelne rozwiązanie, takie jak:

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

Uruchamianie testów porównawczych:

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

Mam nadzieję, że to komuś pomoże!

SRack
źródło
4
różnica jest załamana w tym przypadku a = [1, 2, 3] b = [1, 2, 3, 4, 5] a. różnica (b). jakikolwiek? => fałsz
błąd-404
16

Kiedy elementy ai bComparable,

a.sort == b.sort

Korekta odpowiedzi @ mori na podstawie komentarza @ steenslag

Jared Beck
źródło
1
Miło i rozsądnie.
Erwin Rooijakkers
4
... kiedy ai bmożna sortować.
Cary Swoveland
8

Jeśli oczekujesz [:a, :b] != [:a, :a, :b] to_set, nie działa. Zamiast tego możesz użyć częstotliwości:

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
Victor Moroz
źródło
dlaczego nie tylko a.sort == b.sortjeśli zależy mu na częstotliwości?
fl00r
4
@ fl00r A co, jeśli elementy nie są porównywalne? ["", :b].frequency == [:b, ""].frequency #=> true
Victor Moroz
2
możesz też zrobić coś funkcjonalnego, jaka.group_by{|i| i} == b.group_by{|i| i}
fl00r
7

Jeśli wiesz, że tablice mają równą długość i żadna z nich nie zawiera duplikatów, to też działa:

( array1 & array2 ) == array1

Objaśnienie:& operator w tym przypadku zwraca kopię a1 sans jakieś elementy nie znaleziono w A2, która jest taka sama jak oryginalna a1 IFF obie tablice mają takie same treści bez żadnych duplikatów.

Analiza: Biorąc pod uwagę, że kolejność pozostaje niezmieniona, domyślam się, że jest to implementowane jako podwójna iteracja tak konsekwentnie O(n*n), co jest szczególnie gorsze w przypadku dużych tablic niż te, a1.sort == a2.sortktóre powinny działać w najgorszym przypadku O(n*logn).

Nat
źródło
2
Nie zawsze działa: a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2wraca [2,1,3]dla mnie, co nie jest równea1
Kalyan Raghu
@Kaylan, czy nie masz na myśli, że działa tylko wtedy, gdy a1==a2? Może działać, jeśli array1po prawej stronie równość zostanie zastąpiona przez array2, ale wątpię, aby kolejność zwracanych elementów &była gwarantowana.
Cary Swoveland
1
@KalyanRaghu &jest ustawionym operatorem przecięcia dla tablic, &&jest logiczne ORAZ - są bardzo różne!
Kimball
3

łączenie &i sizemoże być również szybkie.

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
Todoroki
źródło
2

Jednym ze sposobów jest iteracja po tablicy bez duplikatów

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

Zwraca tablicę prawd. Jeśli pojawi się jakiekolwiek fałsz, to zewnętrzne include?zwróci wartość true. Dlatego musisz odwrócić całość, aby określić, czy pasuje.

Ron
źródło
@Victor Moroz, masz rację, a liczba częstotliwości byłaby po prostu O (n).
Ron,