So bestimmen Sie, ob ein Array alle Elemente eines anderen Arrays enthält

177

Gegeben:

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

Ich möchte feststellen, ob es alle Elemente enthält von:

a2 = [2, 6, 15]

In diesem Fall ist das Ergebnis false.

Gibt es integrierte Ruby / Rails-Methoden, um eine solche Array-Aufnahme zu identifizieren?

Eine Möglichkeit, dies zu implementieren, ist:

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

Gibt es einen besseren, besser lesbaren Weg?

Mischa Moroshko
quelle
Die akzeptierte Antwort (Array-Subtraktion) ist die schnellste Lösung. Ich habe sie alle hier verglichen
brainbag

Antworten:

307
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
quelle
60
Dies ist der richtige Weg. Es könnte nur ein bisschen verkürzt werden(a2-a1).empty?
Holger Just
9
Dies funktioniert nur für Arrays, die Sets sind, nicht für Arrays mit Duplikaten
Chris
3
@Chris - Sie könnten versuchen, Array # uniq dafür zu verwenden. Mit Holger Justs Beispiel wäre es(a2.uniq - a1.uniq).empty?
Nick
stackoverflow.com/questions/13553822/… meinte ich. Array # unique schlägt dies explizit fehl.
Chris
81

Vielleicht ist das leichter zu lesen:

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

Sie können auch die Array-Schnittmenge verwenden:

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

Beachten Sie, dass dies sizehier nur aus Geschwindigkeitsgründen verwendet wird. Sie können dies auch tun (langsamer):

(a1 & a2) == a1

Aber ich denke, der erste ist besser lesbar. Diese 3 sind einfach rubinrot (keine Schienen).

Pablo Fernandez
quelle
Wenn Sie die OP-Definition von a1 und a2 verwenden und a1 "alle Elemente von" a2 enthält, sollte dies _ (a1 & a2) sein .size == a2.size _, da a2 das kleinere Array ist, das alle haben sollte Elemente, die im größeren Array enthalten sind (um 'true' zu erhalten) - daher sollte der Schnittpunkt der beiden Arrays dieselbe Länge haben wie das kleinere Array, wenn alle darin enthaltenen Elemente im größeren Array vorhanden sind.
JosephK
57

Dies kann dadurch erreicht werden

(a2 & a1) == a2

Dadurch wird der Schnittpunkt beider Arrays erstellt, wobei alle Elemente zurückgegeben werden, von a2denen sich auch in befindet a1. Wenn das Ergebnis dasselbe ist wie a2, können Sie sicher sein, dass alle Elemente enthalten sind a1.

Dieser Ansatz funktioniert nur, wenn sich alle Elemente in a2erster Linie voneinander unterscheiden. Wenn es Doppel gibt, schlägt dieser Ansatz fehl. Der von Tempos funktioniert dann immer noch, daher empfehle ich seinen Ansatz von ganzem Herzen (auch ist er wahrscheinlich schneller).

Holger Just
quelle
2
mit der lengthMethode wird viel besser
Pablo Fernandez
3
Dies funktioniert nicht, wenn die Schnittmenge dieselben Elemente in einer anderen Reihenfolge enthält. Ich habe das auf die harte Tour herausgefunden, als ich versucht habe, diese Frage zu beantworten: stackoverflow.com/questions/12062970/… später wurde mir klar, dass viele kluge Leute es hier bereits getan hatten!
CubaLibre
1
@CubaLibre Interessant. Haben Sie einige Testdaten, um dies zu reproduzieren? Bei meinen Tests schien es, als ob das resultierende Array die Reihenfolge der Elemente aus dem ersten Array beibehält (daher meine letzte Bearbeitung meiner Antwort). Wenn dies jedoch tatsächlich nicht der Fall ist, würde ich gerne lernen.
Holger Nur
@HolgerJust Ich hatte den Fehler gemacht, (a1 & a2) anstelle von (a2 & a1) zu machen, weshalb ich den Fehler sah. Sie haben Recht damit, die Reihenfolge des ersten Arrays beizubehalten.
CubaLibre
10

Wenn es keine doppelten Elemente gibt oder Sie sich nicht darum kümmern, können Sie die Set- Klasse verwenden:

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

Hinter den Kulissen verwendet dies

all? { |o| set.include?(o) }
Verwirrtheit
quelle
1

Sie können die Array-Klasse mit Affen patchen:

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

Prüfung

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

Natürlich kann die Methode als Standard-Alone-Methode geschrieben werden, z

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

und Sie können es wie aufrufen

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

In der Tat ist die folgende Version nach der Profilerstellung viel schneller und der Code ist kürzer.

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

Abhängig davon, wie groß Ihre Arrays sind, können Sie einen effizienten Algorithmus O (n log n) in Betracht ziehen.

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

Das Sortieren der Kosten O (n log n) und das Überprüfen jedes Paares kostet O (n), daher ist dieser Algorithmus O (n log n). Die anderen Algorithmen können mit unsortierten Arrays nicht schneller (asymptotisch) sein.

Ayckoster
quelle
Sie können dies in O (n) mit einer Zählsortierung tun.
Klochner
Nein, du kannst nicht. Das Zählen der Sortierung verwendet ein begrenztes Universum und Ruby hat keine Einschränkung, wie große Zahlen erhalten werden können.
Ayckoster
Sie können, weil Sie die Elemente nicht wirklich sortieren müssen - Sie benötigen lediglich ein Hash-Mapping-Element -> für beide Arrays zählen, dann über die Schlüssel iterieren und die Anzahl vergleichen.
Klochner
Sind Sie sicher, dass Array # sort die Zusammenführungssortierung verwendet?
Nate Symer
0

Die meisten Antworten, die auf (a1 - a2) oder (a1 & a2) basieren, würden nicht funktionieren, wenn in einem der Arrays doppelte Elemente vorhanden sind. Ich bin hier angekommen, um herauszufinden, ob alle Buchstaben eines Wortes (aufgeteilt in ein Array) Teil einer Reihe von Buchstaben sind (zum Beispiel für Scrabble). Keine dieser Antworten hat funktioniert, aber diese:

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