Comment déterminer si un tableau contient tous les éléments d'un autre tableau

184

Donné:

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

Je voudrais déterminer s'il contient tous les éléments de:

a2 = [2, 6, 15]

Dans ce cas, le résultat est false.

Existe-t-il des méthodes Ruby / Rails intégrées pour identifier une telle inclusion de tableau?

Une façon de mettre en œuvre ceci est:

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

Existe-t-il une meilleure façon, plus lisible?

Misha Moroshko
la source
1
La réponse acceptée (soustraction de tableau) est la solution la plus rapide. Je les ai tous comparés
brainbag

Réponses:

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

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

b - a
# => [15]

(b - a).empty?
# => false
Géo
la source
62
C'est la voie à suivre. Cela pourrait être un peu raccourci à(a2-a1).empty?
Holger Just
9
Cela ne fonctionne que pour les tableaux qui sont des ensembles, pas pour les tableaux avec des doublons
Chris
3
@Chris - Vous pouvez essayer d'utiliser Array # uniq pour cela. Avec l'exemple de Holger Just, ce serait(a2.uniq - a1.uniq).empty?
Nick
stackoverflow.com/questions/13553822/… est ce que je voulais dire. Array # unique échouera explicitement.
Chris
83

C'est peut-être plus facile à lire:

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

Vous pouvez également utiliser l'intersection de tableau:

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

Notez que sizec'est utilisé ici uniquement pour la vitesse, vous pouvez également faire (plus lentement):

(a1 & a2) == a1

Mais je suppose que le premier est plus lisible. Ces 3 sont du rubis uni (pas des rails).

Pablo Fernandez
la source
Si vous utilisez la définition de l'OP de a1 et a2, et a1 "contenant tous les éléments de" a2, je pense que cela devrait être _ (a1 & a2) .size == a2.size _ puisque a2 est le plus petit tableau, qui devrait avoir tout éléments inclus dans le plus grand tableau (pour obtenir «vrai») - par conséquent, l'intersection des deux tableaux doit être de la même longueur que le plus petit tableau si tous les éléments qu'il contient sont présents dans le plus grand.
JosephK
58

Ceci peut être réalisé en faisant

(a2 & a1) == a2

Cela crée l'intersection des deux tableaux, renvoyant tous les éléments à partir a2desquels se trouvent également a1. Si le résultat est le même que a2, vous pouvez être sûr que tous les éléments sont inclus dans a1.

Cette approche ne fonctionne que si tous les éléments de a2sont différents les uns des autres en premier lieu. S'il y a des doubles, cette approche échoue. Celui de Tempos fonctionne toujours alors, je recommande donc de tout cœur son approche (aussi c'est probablement plus rapide).

Holger Just
la source
2
utiliser la lengthméthode fonctionnera beaucoup mieux
Pablo Fernandez
3
Cela ne fonctionnera pas si le jeu d'intersection contient les mêmes éléments dans un ordre différent. J'ai découvert cela à la dure en essayant de répondre à cette question: stackoverflow.com/questions/12062970/ ... j'ai réalisé plus tard que beaucoup de gens intelligents l'avaient déjà fait ici!
CubaLibre
1
@CubaLibre Intéressant. Avez-vous des données de test pour reproduire cela? D'après mes tests, il semblait que le tableau résultant conserve l'ordre des éléments du premier tableau (d'où ma dernière modification de ma réponse). Cependant, si ce n'est effectivement pas le cas, j'aimerais apprendre.
Holger juste
@HolgerJuste j'avais commis l'erreur de faire (a1 & a2) au lieu de (a2 & a1), c'est pourquoi je voyais l'erreur. Vous avez raison de conserver l'ordre du premier tableau.
CubaLibre
10

S'il n'y a pas d'éléments en double ou que vous ne vous souciez pas d'eux, vous pouvez utiliser la classe Set :

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

Dans les coulisses, cela utilise

all? { |o| set.include?(o) }
Confusion
la source
1

Vous pouvez monkey-patcher la classe Array:

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

tester

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

Bien sûr, la méthode peut être écrite comme une méthode standard, par exemple

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

et vous pouvez l'invoquer comme

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

En effet, après profilage, la version suivante est beaucoup plus rapide, et le code est plus court.

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

En fonction de la taille de vos tableaux, vous pouvez envisager un algorithme efficace 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

Le tri coûte O (n log n) et la vérification de chaque paire coûte O (n) donc cet algorithme est O (n log n). Les autres algorithmes ne peuvent pas être plus rapides (asymptotiquement) en utilisant des tableaux non triés.

ayckoster
la source
Vous pouvez le faire en O (n) avec un tri par comptage.
klochner le
Non vous ne pouvez pas. Le tri de comptage utilise un univers limité et Ruby n'a pas de limitation sur la façon dont les grands nombres peuvent obtenir.
ayckoster le
Vous pouvez, parce que vous n'avez pas vraiment besoin de trier les éléments - vous avez juste besoin d'un élément de mappage de hachage -> compter pour les deux tableaux, puis parcourir les clés et comparer les nombres.
klochner le
Êtes-vous sûr que Array # sort utilise le tri par fusion?
Nate Symer
0

La plupart des réponses basées sur (a1 - a2) ou (a1 & a2) ne fonctionneraient pas s'il y a des éléments en double dans l'un ou l'autre des tableaux. Je suis arrivé ici à la recherche d'un moyen de voir si toutes les lettres d'un mot (divisé en un tableau) faisaient partie d'un ensemble de lettres (pour le scrabble par exemple). Aucune de ces réponses n'a fonctionné, mais celle-ci fonctionne:

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