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?


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

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).


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).


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) }

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

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.


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.