Compruebe si dos matrices tienen el mismo contenido (en cualquier orden)

90

Estoy usando Ruby 1.8.6 con Rails 1.2.3 y necesito determinar si dos matrices tienen los mismos elementos, independientemente de si están o no en el mismo orden. Se garantiza que una de las matrices no contiene duplicados (la otra podría, en cuyo caso la respuesta es no).

Mi primer pensamiento fue

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

pero me preguntaba si había una forma más eficiente o idiomática de hacerlo.

Taymon
fuente
Pruebe array.should = ~ another_array check stackoverflow.com/questions/2978922/…
Athena
Podría haberse ahorrado mucha confusión al: 1) indicar si los elementos de las matrices son necesariamente ordenables; y 2) proporcionar un ejemplo simple para aclarar lo que quiere decir con "si dos matrices tienen los mismos elementos" (por ejemplo, ¿ tienen [1,2]y [2,1,1]tienen los mismos elementos?)
Cary Swoveland
Se ha introducido Ruby 2.6, differenceque ofrece una solución muy rápida y muy legible. Más info aquí.
SRack el

Respuestas:

141

Esto no requiere conversión para configurar:

a.sort == b.sort
Mori
fuente
¿Sin conversión? ¿Qué es .uniq.sortentonces? Además uniqes similar a to_setinternamente más adicional.to_a.sort
Victor Moroz
Aceptando esto porque es lo más cercano a lo que terminé usando, aunque sin la uniqs. En realidad terminé creando una de las matrices con Range#to_a, así que solo tuve sortla otra.
Taymon
11
Esto no funcionará si la matriz contiene elementos que no se pueden ordenar simplemente (por ejemplo, una matriz de hashes). La solución de sahil dhankhar parece ser una solución más general.
Brad
40

para dos matrices A y B: A y B tienen el mismo contenido si: (A-B).blank? and (B-A).blank?

o simplemente puede verificar: ((A-B) + (B-A)).blank?

También como lo sugiere @ cort3z, esta solución también funciona para matrices polimórficas, es decir

 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` 

::::::::::: EDITAR :::::::::::::

Como se sugiere en los comentarios, la solución anterior falla para los duplicados, aunque según la pregunta, eso ni siquiera es obligatorio, ya que el autor de la pregunta no está interesado en los duplicados (está convirtiendo sus matrices para establecer antes de verificar y eso enmascara los duplicados e incluso si miras la respuesta aceptada está usando un operador .uniq antes de verificar y eso también enmascara los duplicados). Pero aún así, si le interesan los duplicados, solo agregar una verificación de conteo solucionará lo mismo (según la pregunta, solo una matriz puede contener duplicados). Entonces la solución final será: A.size == B.size and ((A-B) + (B-A)).blank?

Sahil Dhankhar
fuente
Esto fallará si alguna de las matrices contiene duplicados. Por ejemplo, si A=[1]y B=[1,1], ambos (A-B)y (B-A)volverán en blanco. Consulte la documentación de la matriz .
jtpereyda
@dafrazzman está totalmente de acuerdo contigo. Modifiqué mi respuesta para incorporar sus comentarios, pero si observa de cerca la pregunta (o la respuesta aceptada), el autor de la pregunta usa: a.to_set == b.to_set y la respuesta aceptada usa a.uniq.sort == b.uniq.sorty ambos dan exactamente el mismo resultado que ((A-B) + (B-A)).blank?para A = [1] y B = [1,1] de acuerdo? Como solo estaba pidiendo una mejora con respecto a su solución original, mi solución original todavía funciona :). ¿de acuerdo?
Sahil Dhankhar
1
Esta solución es bastante buena ya que maneja objetos de múltiples tipos. Digamos que tiene A = [123, "test", [], some_object, nil]y B = A#because I am lazy, luego A.uniq.sortarrojará un error (la comparación de la cadena y la matriz fallaron).
Automático
¿Sería O (n) entonces, ya que depende del tamaño de la matriz? (lineal)
user3007294
1
No funcionaría si las matrices tienen el mismo tamaño pero los elementos repetidos no son los mismos. Por ejemplo A = [1, 1, 2]yB = [1, 2, 2]
Boudi
23

Comparaciones de velocidad

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
fuente
por cierto, el orden de los elemetns no afecta sortla velocidad
Morozov
Me sorprendió ... Esperaba que la comparación por conjuntos superara a todos los demás debido a la complejidad del tiempo de búsqueda de conjuntos O (n). De modo que cualquier tipo bien implementado requeriría O (n logn). Mientras que la conversión a conjuntos y la búsqueda de valores lo haría en general en O (n) tiempo.
Oleg Afanasyev
1
Esperaría to_setcomenzar a tener un rendimiento superior con matrices lo suficientemente grandes donde O (n logn) comenzaría a importar más que el esfuerzo requerido para convertir una matriz en un conjunto
Andrius Chamentauskas
1
Esto es útil, pero ¿no es realmente una respuesta en sí misma? ¿Quizás sea mejor agregar esto a una solución existente?
SRack el
19

Ruby 2.6+

Ruby se introdujo differenceen 2.6.

Esto da una solución muy rápida y muy legible aquí, como sigue:

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

Ejecución de los puntos de referencia:

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

¡Espero que ayude a alguien!

SRack
fuente
4
la diferencia se está rompiendo en este caso a = [1, 2, 3] b = [1, 2, 3, 4, 5] a.diferencia (b). => falso
error-404
16

Cuando los elementos de ay bson Comparable,

a.sort == b.sort

Corrección de la respuesta de @ mori basada en el comentario de @ steenslag

Jared Beck
fuente
1
Agradable y razonable.
Erwin Rooijakkers
4
... cuándo ay bse puede ordenar.
Cary Swoveland
8

Si lo espera [:a, :b] != [:a, :a, :b] to_set, no funciona. En su lugar, puede utilizar la frecuencia:

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
Víctor Moroz
fuente
¿por qué no solo a.sort == b.sortsi le importa la frecuencia?
fl00r
4
@ fl00r ¿Qué pasa si los artículos no son comparables? ["", :b].frequency == [:b, ""].frequency #=> true
Victor Moroz
2
también puede hacer algo funcional comoa.group_by{|i| i} == b.group_by{|i| i}
fl00r
7

Si sabe que las matrices son de la misma longitud y ninguna matriz contiene duplicados, esto también funciona:

( array1 & array2 ) == array1

Explicación: el &operador en este caso devuelve una copia de a1 sin ningún elemento que no se encuentre en a2, que es el mismo que el a1 original si ambos arreglos tienen el mismo contenido sin duplicados.

Análisis: Dado que el orden no ha cambiado, supongo que esto se implementa como una iteración doble de manera tan consistente O(n*n), notablemente peor para matrices grandes que las a1.sort == a2.sortque deberían funcionar en el peor de los casos O(n*logn).

Nat
fuente
2
No siempre funciona: a1 = [1,2,3], a2 = [2, 1, 3] a1 && a2devuelve [2,1,3]para mí que no es igual aa1
Kalyan Raghu
@Kaylan, ¿no quieres decir que solo funciona cuando a1==a2? Puede funcionar si array1en el lado derecho de la igualdad se reemplaza por array2, pero dudo que el orden de los elementos devueltos por &esté garantizado.
Cary Swoveland
1
@KalyanRaghu &es un operador de intersección de conjuntos para matrices, &&es lógico Y, ¡son muy diferentes!
Kimball
3

combinando &y sizepuede ser rápido también.

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

Un enfoque es iterar sobre la matriz sin duplicados

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

Esto devuelve una serie de verdades. Si aparece algo falso, el exterior include?devolverá verdadero. Por lo tanto, debe invertir todo para determinar si coincide.

Ron
fuente
@Victor Moroz, tienes razón, y un recuento de frecuencia sería simplemente O (n).
Ron