Controlla se due array hanno lo stesso contenuto (in qualsiasi ordine)


90

Sto usando Ruby 1.8.6 con Rails 1.2.3 e devo determinare se due array hanno gli stessi elementi, indipendentemente dal fatto che siano o meno nello stesso ordine. È garantito che uno degli array non contenga duplicati (l'altro potrebbe, nel qual caso la risposta è no).

Il mio primo pensiero è stato

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

ma mi chiedevo se ci fosse un modo più efficiente o idiomatico per farlo.



Prova array.should = ~ another_array controlla stackoverflow.com/questions/2978922/…
Athena

Avresti potuto risparmiare molta confusione: 1) affermando se gli elementi degli array sono necessariamente ordinabili; e 2) fornire un semplice esempio per chiarire cosa intendi con "se due array hanno gli stessi elementi" (ad esempio, hanno [1,2]e [2,1,1]hanno gli stessi elementi?)
Cary Swoveland

Ruby 2.6 ha introdotto differenceche offre una soluzione sia molto veloce che molto leggibile. Maggiori info qui.
SRack

Risposte:


140

Ciò non richiede la conversione per impostare:

a.sort == b.sort

Nessuna conversione? Cos'è .uniq.sortallora? Inoltre uniqè simile a to_setinternamente più aggiuntivo.to_a.sort
Victor Moroz

Accettandolo poiché è il più vicino a quello che ho finito per usare, anche se senza la uniqs. In realtà ho finito per creare uno degli array con Range#to_a, quindi ho dovuto solo sortl'altro.
Taymon

11
Questo non funzionerà se l'array contiene elementi che non possono essere semplicemente ordinati (ad esempio un array di hash). La soluzione di Sahil Dhankhar sembra essere una soluzione più generale.
Brad

40

per due array A e B: A e B hanno lo stesso contenuto se: (A-B).blank? and (B-A).blank?

oppure puoi semplicemente controllare: ((A-B) + (B-A)).blank?

Inoltre, come suggerito da @ cort3z, questa soluzione funziona anche per array polimorfici es

 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` 

::::::::::: MODIFICARE :::::::::::::

Come suggerito nei commenti, la soluzione di cui sopra non riesce per i duplicati Anche se come per la domanda che non è nemmeno richiesta poiché il richiedente non è interessato ai duplicati (sta convertendo i suoi array in set prima di controllare e che maschera i duplicati e anche se guardi la risposta accettata sta usando un operatore .uniq prima di controllare e anche questo maschera i duplicati.). Tuttavia, se i duplicati ti interessano, basta aggiungere un controllo del conteggio risolverà lo stesso (come per la domanda solo un array può contenere duplicati). Quindi la soluzione finale sarà: A.size == B.size and ((A-B) + (B-A)).blank?


Questo fallirà se uno degli array contiene duplicati. Ad esempio, se A=[1]e B=[1,1], entrambi (A-B)e (B-A)torneranno vuoti. Vedere la documentazione sugli array .
jtpereyda

@dafrazzman totalmente d'accordo con te. Ho modificato la mia risposta per incorporare il tuo feedback, ma se osservi attentamente la domanda (o la risposta accettata), il richiedente sta usando: a.to_set == b.to_set e la risposta accettata sta usando a.uniq.sort == b.uniq.sorted entrambi danno esattamente lo stesso risultato ((A-B) + (B-A)).blank?di A = [1] e B = [1,1] d'accordo? Dato che stava solo chiedendo un miglioramento rispetto alla sua soluzione originale, la mia soluzione originale funziona ancora :). essere d'accordo?
Sahil Dhankhar

1
Questa soluzione è abbastanza carina poiché gestisce oggetti di più tipi. Supponiamo di avere A = [123, "test", [], some_object, nil]e B = A#because I am lazy, quindi A.uniq.sortverrà generato un errore (confronto tra stringa e array non riuscito).
Automatico

Sarebbe quindi O (n) poiché dipende dalla dimensione dell'array? (lineare)
user3007294

1
Non funzionerebbe se gli array hanno la stessa dimensione ma gli elementi ripetuti non sono gli stessi. Ad esempio A = [1, 1, 2]eB = [1, 2, 2]
Boudi

23

Confronti di velocità

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

tra l'altro l'ordine degli elettrocateteri non influisce sortsulla velocità
Morozov

Mi ha sorpreso ... Mi aspettavo che il confronto per set superasse tutti gli altri a causa della complessità temporale della ricerca di set O (n). In modo che qualsiasi ordinamento ben implementato richiederebbe O (n logn). Mentre il casting su set e la ricerca di valori lo farebbero in un tempo O (n).
Oleg Afanasyev

1
Mi aspetto to_setdi iniziare a sovraperformare con array abbastanza grandi in cui O (n logn) inizierebbe ad avere importanza più dello sforzo richiesto per convertire l'array in set
Andrius Chamentauskas

1
Questo è utile, ma non è davvero una risposta in sé? Forse è meglio aggiungerlo a una soluzione esistente?
SRack

18

Ruby 2.6+

Ruby è stato introdotto differencenella 2.6.

Questo fornisce una soluzione molto veloce e molto leggibile qui, come segue:

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

Esecuzione dei benchmark:

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

Spero che aiuti qualcuno!


2
la differenza è rottura in questo caso a = [1, 2, 3] b = [1, 2, 3, 4, 5] a. differenza (b) .any? => false
errore-404


8

Se ti aspetti [:a, :b] != [:a, :a, :b] to_setnon funziona. Puoi invece usare la frequenza:

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

perché non solo a.sort == b.sortse gli interessa la frequenza?
fl00r

4
@ fl00r Cosa succede se gli articoli non sono confrontabili? ["", :b].frequency == [:b, ""].frequency #=> true
Victor Moroz

2
inoltre puoi fare qualcosa di funzionale comea.group_by{|i| i} == b.group_by{|i| i}
fl00r

7

Se sai che gli array hanno la stessa lunghezza e nessuno dei due array contiene duplicati, anche questo funziona:

( array1 & array2 ) == array1

Spiegazione: il& operatore in questo caso restituisce una copia di a1 senza gli elementi non trovati in a2, che è lo stesso dell'originale a1 se e solo se entrambi gli array hanno lo stesso contenuto senza duplicati.

Analisi: dato che l'ordine è invariato, immagino che sia implementato come una doppia iterazione in modo così coerente O(n*n), notevolmente peggiore per array di grandi dimensioni rispetto a quello a1.sort == a2.sortche dovrebbe funzionare nel caso peggiore O(n*logn).


2
Non funziona sempre: a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2ritorna [2,1,3]per me che non è uguale aa1
Kalyan Raghu

@ Kaylan, non vuoi dire che funziona solo quando a1==a2? Può funzionare se array1sul lato destro dell'uguaglianza è sostituito da array2, ma dubito che l'ordine degli elementi restituiti da &sia garantito.
Cary Swoveland

1
@KalyanRaghu &è un operatore di intersezione di insiemi per array, &&è logico E - sono molto diversi!
Kimball

2

Un approccio consiste nell'iterare sull'array senza duplicati

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

Ciò restituisce un array di true. Se viene visualizzato un falso, l'esterno include?restituirà vero. Quindi devi invertire l'intera cosa per determinare se è una corrispondenza.


@ Victor Moroz, hai ragione, e un conteggio di frequenza sarebbe semplicemente O (n).
Ron

2

combinando &e sizepuò essere anche veloce.

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.