Cómo determinar si una matriz contiene todos los elementos de otra matriz

179

Dado:

a1 = [5, 1, 6, 14, 2, 8]

Me gustaría determinar si contiene todos los elementos de:

a2 = [2, 6, 15]

En este caso el resultado es false.

¿Hay algún método incorporado de Ruby / Rails para identificar dicha inclusión de matriz?

Una forma de implementar esto es:

a2.index{ |x| !a1.include?(x) }.nil?

¿Hay una manera mejor y más legible?

Misha Moroshko
fuente
La respuesta aceptada (sustracción de matriz) es la solución más rápida. Los comparé
brainbag

Respuestas:

309
a = [5, 1, 6, 14, 2, 8]
b = [2, 6, 15]

a - b
=> [5, 1, 14, 8]

b - a
=> [15]

(b - a).empty?
=> false
Geo
fuente
61
Este es el camino a seguir. Puede que se acorte un poco a(a2-a1).empty?
Holger Solo el
9
Esto solo funciona para matrices que son conjuntos, no para matrices con duplicados
Chris
3
@ Chris - Puedes intentar usar Array # uniq para eso. Con el ejemplo de Holger Just, sería(a2.uniq - a1.uniq).empty?
Nick
stackoverflow.com/questions/13553822/… es lo que quise decir. La matriz # única explícitamente fallará eso.
Chris
81

Quizás esto sea más fácil de leer:

a2.all? { |e| a1.include?(e) }

También puede usar la intersección de matriz:

(a1 & a2).size == a1.size

Tenga en cuenta que sizese usa aquí solo para la velocidad, también puede hacer (más lento):

(a1 & a2) == a1

Pero supongo que el primero es más legible. Estos 3 son rubí simple (no rieles).

Pablo Fernández
fuente
Si usa la definición de OP de a1 y a2, y a1 "que contiene todos los elementos de" a2, creo que esto debería ser _ (a1 y a2) .size == a2.size _ ya que a2 es la matriz más pequeña, que debería tener todos elementos incluidos en la matriz más grande (para obtener 'verdadero'); por lo tanto, la intersección de las dos matrices debe ser de la misma longitud que la matriz más pequeña si todos los elementos están presentes en la matriz más grande.
JosephK
57

Esto se puede lograr haciendo

(a2 & a1) == a2

Esto crea la intersección de ambas matrices, devolviendo todos los elementos desde los a2cuales también se encuentran a1. Si el resultado es el mismo a2, puede estar seguro de que tiene todos los elementos incluidos a1.

Este enfoque solo funciona si todos los elementos a2son diferentes entre sí en primer lugar. Si hay dobles, este enfoque falla. El de Tempos todavía funciona entonces, así que recomiendo su enfoque (también es probablemente más rápido).

Holger Just
fuente
2
usar el lengthmétodo funcionará mucho mejor
Pablo Fernández
3
Esto no funcionará si el conjunto de intersección tiene los mismos elementos en un orden diferente. Descubrí esto de la manera difícil al intentar responder a esta pregunta: stackoverflow.com/questions/12062970/... luego me di cuenta de que muchas personas inteligentes ya lo habían hecho aquí.
CubaLibre
1
@CubaLibre Interesante. ¿Tienes algunos datos de prueba para reproducir esto? De mis pruebas parecía que la matriz resultante conserva el orden de los elementos de la primera matriz (de ahí mi edición más reciente de mi respuesta). Sin embargo, si este no es el caso, me gustaría aprender.
Holger Solo el
@HolgerJust cometí el error de hacer (a1 y a2) en lugar de (a2 y a1), por lo que estaba viendo el error. Tiene razón y retiene el pedido de la primera matriz.
CubaLibre
10

Si no hay elementos duplicados o no le importan, puede usar la clase Set :

a1 = Set.new [5, 1, 6, 14, 2, 8]
a2 = Set.new [2, 6, 15]
a1.subset?(a2)
=> false

Detrás de escena esto usa

all? { |o| set.include?(o) }
Confusión
fuente
1

Puede parchear la clase Array:

class Array
    def contains_all?(ary)
        ary.uniq.all? { |x| count(x) >= ary.count(x) }
    end
end

prueba

irb(main):131:0> %w[a b c c].contains_all? %w[a b c]
=> true
irb(main):132:0> %w[a b c c].contains_all? %w[a b c c]
=> true
irb(main):133:0> %w[a b c c].contains_all? %w[a b c c c]
=> false
irb(main):134:0> %w[a b c c].contains_all? %w[a]
=> true
irb(main):135:0> %w[a b c c].contains_all? %w[x]
=> false
irb(main):136:0> %w[a b c c].contains_all? %w[]
=> true
irb(main):137:0> %w[a b c d].contains_all? %w[d c h]
=> false
irb(main):138:0> %w[a b c d].contains_all? %w[d b c]
=> true

Por supuesto, el método puede escribirse como un método estándar solo, p. Ej.

def contains_all?(a,b)
    b.uniq.all? { |x| a.count(x) >= b.count(x) }
end

y puedes invocarlo como

contains_all?(%w[a b c c], %w[c c c])

De hecho, después de la creación de perfiles, la siguiente versión es mucho más rápida y el código es más corto.

def contains_all?(a,b)
    b.all? { |x| a.count(x) >= b.count(x) }
end
Zack Xu
fuente
0

Dependiendo de qué tan grandes sean sus matrices, podría considerar un algoritmo eficiente O (n log n)

def equal_a(a1, a2)
  a1sorted = a1.sort
  a2sorted = a2.sort
  return false if a1.length != a2.length
  0.upto(a1.length - 1) do 
    |i| return false if a1sorted[i] != a2sorted[i]
  end
end

La clasificación cuesta O (n log n) y la comprobación de cada par cuesta O (n), por lo tanto este algoritmo es O (n log n). Los otros algoritmos no pueden ser más rápidos (asintóticamente) usando matrices sin clasificar.

ayckoster
fuente
Puede hacerlo en O (n) con un tipo de conteo.
klochner
No, no puedes. El ordenamiento de conteo utiliza un universo limitado y Ruby no tiene una limitación sobre cómo pueden obtener grandes números.
ayckoster
Puede hacerlo, porque en realidad no tiene que ordenar los elementos, solo necesita un elemento de mapeo hash -> contar para ambas matrices, luego iterar sobre las claves y comparar recuentos.
klochner
¿Está seguro de que Array # sort usa merge sort?
Nate Symer
0

La mayoría de las respuestas basadas en (a1 - a2) o (a1 y a2) no funcionarían si hay elementos duplicados en cualquier matriz. Llegué aquí buscando una manera de ver si todas las letras de una palabra (divididas en una matriz) eran parte de un conjunto de letras (por ejemplo, scrabble). Ninguna de estas respuestas funcionó, pero esta sí:

def contains_all?(a1, a2)
  try = a1.chars.all? do |letter|
    a1.count(letter) <= a2.count(letter)
  end
  return try
end
Charles Breton
fuente