Jak ustalić, czy jedna tablica zawiera wszystkie elementy innej tablicy

179

Dany:

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

Chciałbym ustalić, czy zawiera wszystkie elementy:

a2 = [2, 6, 15]

W tym przypadku wynikiem jest false.

Czy są jakieś wbudowane metody Ruby / Rails do identyfikacji takiego włączenia tablicy?

Jednym ze sposobów realizacji tego jest:

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

Czy istnieje lepszy, bardziej czytelny sposób?

Misha Moroshko
źródło
Zaakceptowana odpowiedź (odejmowanie tablicy) jest najszybszym rozwiązaniem. Testowałem
brainbag

Odpowiedzi:

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
źródło
61
To jest droga do zrobienia. To może być trochę skrócone do(a2-a1).empty?
Holger Just
9
Działa to tylko dla tablic, które są zestawami, a nie dla tablic z duplikatami
Chris
3
@Chris - Możesz do tego spróbować użyć Array # uniq. Na przykładzie Holgera Justa byłoby to(a2.uniq - a1.uniq).empty?
Nick
stackoverflow.com/questions/13553822/ ... właśnie miałem na myśli. Array # unique wyraźnie to zawiedzie.
Chris,
81

Być może łatwiej to przeczytać:

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

Możesz również użyć przecięcia tablicy:

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

Zauważ, że sizejest tu używane tylko dla szybkości, możesz również zrobić (wolniej):

(a1 & a2) == a1

Ale myślę, że pierwszy jest bardziej czytelny. Te 3 to zwykły rubin (bez szyn).

Pablo Fernandez
źródło
Jeśli używasz definicji OP a1 i a2 oraz a1 "zawierającej wszystkie elementy" a2, myślę, że powinno to być _ (a1 i a2) .size == a2.size _ ponieważ a2 jest mniejszą tablicą, która powinna mieć wszystkie elementy zawarte w większej tablicy (w celu uzyskania „prawdziwej”) - stąd przecięcie dwóch tablic powinno mieć taką samą długość jak mniejsza tablica, jeśli wszystkie zawarte w niej elementy są obecne w większej.
JosephK
57

Można to osiągnąć poprzez działanie

(a2 & a1) == a2

Tworzy to przecięcie obu tablic, zwracając wszystkie elementy, z a2których również się znajdują a1. Jeśli wynik jest taki sam jak a2, możesz być pewien, że masz wszystkie elementy zawarte w a1.

To podejście działa tylko wtedy, gdy wszystkie elementy a2są różne od siebie w pierwszej kolejności. Jeśli są dublety, to podejście zawodzi. Ten z Tempos nadal wtedy działa, więc z całego serca polecam jego podejście (też chyba szybsze).

Holger Just
źródło
2
użycie tej lengthmetody będzie znacznie lepsze
Pablo Fernandez
3
To nie zadziała, jeśli zestaw skrzyżowań zawiera te same elementy w innej kolejności. Przekonałem się o tym na własnej skórze, próbując odpowiedzieć na to pytanie: stackoverflow.com/questions/12062970/ ... później zdałem sobie sprawę, że wielu mądrych ludzi już to zrobiło!
CubaLibre
1
@CubaLibre Ciekawe. Czy masz jakieś dane testowe, aby to odtworzyć? Z moich testów wydawało się, że wynikowa tablica zachowuje kolejność elementów z pierwszej tablicy (stąd moja ostatnia edycja mojej odpowiedzi). Jeśli jednak tak nie jest, chciałbym się dowiedzieć.
Holger Just
@Holger Po prostu popełniłem błąd, wykonując (a1 i a2) zamiast (a2 i a1), dlatego widziałem błąd. Masz rację i zachowujesz kolejność z pierwszej tablicy.
CubaLibre
10

Jeśli nie ma zduplikowanych elementów lub nie przejmujesz się nimi, możesz skorzystać z klasy Set :

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

Za kulisami to wykorzystuje

all? { |o| set.include?(o) }
Dezorientacja
źródło
1

Możesz małpować klasę Array:

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

test

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

Oczywiście metodę można zapisać jako metodę samodzielną, np

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

i możesz to wywołać jak

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

Rzeczywiście, po profilowaniu poniższa wersja jest znacznie szybsza, a kod krótszy.

def contains_all?(a,b)
    b.all? { |x| a.count(x) >= b.count(x) }
end
Zack Xu
źródło
0

W zależności od tego, jak duże są twoje tablice, możesz rozważyć wydajny algorytm 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

Sortowanie kosztów O (n log n) i sprawdzanie każdej pary kosztuje O (n), więc algorytm ten wynosi O (n log n). Inne algorytmy nie mogą być szybsze (asymptotycznie) przy użyciu niesortowanych tablic.

ayckoster
źródło
Możesz to zrobić w O (n) z sortowaniem liczącym.
klochner
Nie, nie możesz. Sortowanie według liczenia wykorzystuje ograniczony wszechświat, a Ruby nie ma ograniczeń co do tego, jak duże liczby mogą uzyskać.
ayckoster
Możesz, ponieważ w rzeczywistości nie musisz sortować elementów - potrzebujesz tylko elementu mapującego hash -> count dla obu tablic, a następnie iteruj po kluczach i porównuj liczby.
klochner
Czy na pewno Array # sort używa merge sort?
Nate Symer,
0

Większość odpowiedzi opartych na (a1 - a2) lub (a1 i a2) nie zadziała, jeśli w którejś z tablic są zduplikowane elementy. Przyjechałem tutaj, szukając sposobu, aby sprawdzić, czy wszystkie litery słowa (podzielone na tablicę) są częścią zestawu liter (na przykład do scrabble). Żadna z tych odpowiedzi nie zadziałała, ale ta:

def contains_all?(a1, a2)
  try = a1.chars.all? do |letter|
    a1.count(letter) <= a2.count(letter)
  end
  return try
end
Charles Breton
źródło