Come determinare se un array contiene tutti gli elementi di un altro array


179

Dato:

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

Vorrei determinare se contiene tutti gli elementi di:

a2 = [2, 6, 15]

In questo caso il risultato è false.

Esistono metodi Ruby / Rails integrati per identificare tale inclusione di array?

Un modo per implementare questo è:

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

Esiste un modo migliore, più leggibile?


La risposta accettata (sottrazione di array) è la soluzione più veloce. Li ho confrontati tutti qui: gist.github.com/bbugh/cbbde8b48cbb16286044f6893e1f2e5f
brainbag

Risposte:


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
Questa è la strada da percorrere. Potrebbe essere un po 'abbreviato in(a2-a1).empty?
Holger, appena il

9
Questo funziona solo per array che sono set, non per array con duplicati
Chris,

3
@Chris - Potresti provare ad usare Array # uniq per questo. Con l'esempio di Holger Just, sarebbe(a2.uniq - a1.uniq).empty?
Nick,

stackoverflow.com/questions/13553822/… è ciò che intendevo. L'array # unique fallirà esplicitamente.
Chris,

81

Forse è più facile da leggere:

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

È inoltre possibile utilizzare l'intersezione di array:

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

Nota che sizeè usato qui solo per la velocità, puoi anche fare (più lentamente):

(a1 & a2) == a1

Ma immagino che il primo sia più leggibile. Questi 3 sono semplici rubini (non binari).


Se si utilizza la definizione OP di a1 e a2 e a1 "contenente tutti gli elementi di" a2, penso che questo dovrebbe essere _ (a1 & a2) .size == a2.size _ poiché a2 è l'array più piccolo, che dovrebbe avere tutti elementi inclusi nell'array più grande (per ottenere "vero") - quindi l'intersezione dei due array dovrebbe avere la stessa lunghezza dell'array più piccolo se tutti gli elementi in esso presenti sono presenti in quello più grande.
JosephK,

57

Questo può essere ottenuto facendo

(a2 & a1) == a2

Questo crea l'intersezione di entrambi gli array, restituendo tutti gli elementi da a2cui si trovano anche a1. Se il risultato è lo stesso di a2, puoi essere sicuro di avere tutti gli elementi inclusi in a1.

Questo approccio funziona solo se tutti gli elementi a2sono diversi tra loro in primo luogo. Se ci sono doppi, questo approccio fallisce. Quello di Tempos funziona ancora allora, quindi consiglio vivamente il suo approccio (anche probabilmente è più veloce).


2
usando il lengthmetodo funzionerà molto meglio
Pablo Fernandez,

3
Questo non funzionerà se il set di intersezioni ha gli stessi elementi in un ordine diverso. L'ho scoperto nel modo più difficile mentre cercavo di rispondere a questa domanda: stackoverflow.com/questions/12062970/… in seguito si sono resi conto che molte persone intelligenti l'avevano già fatto qui!
CubaLibre,

1
@CubaLibre Interessante. Hai alcuni dati di test per riprodurlo? Dai test mi è sembrato che l'array risultante mantenga l'ordine degli elementi dal primo array (quindi la mia modifica più recente alla mia risposta). Tuttavia, se questo non è effettivamente il caso, mi piacerebbe imparare.
Holger, appena il

@HolgerJust Avevo fatto l'errore di fare (a1 e a2) invece di (a2 e a1), motivo per cui stavo vedendo l'errore. Hai ragione e mantieni l'ordine dal primo array.
CubaLibre,

10

Se non ci sono elementi duplicati o non ti interessa, puoi utilizzare la classe Set :

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

Dietro le quinte questo usa

all? { |o| set.include?(o) }

1

Puoi applicare la patch di scimmia alla classe 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

Naturalmente il metodo può essere scritto come un metodo standard solo, ad es

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

e puoi invocarlo come

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

In effetti, dopo la profilazione, la seguente versione è molto più veloce e il codice è più breve.

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

0

A seconda di quanto sono grandi le tue matrici potresti considerare un algoritmo efficiente 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

L'ordinamento dei costi O (n log n) e la verifica di ciascuna coppia costa O (n), pertanto questo algoritmo è O (n log n). Gli altri algoritmi non possono essere più veloci (asintoticamente) utilizzando array non ordinati.


Puoi farlo in O (n) con un ordinamento di conteggio.
Klochner,

No, non puoi. Il conteggio degli ordinamenti utilizza un universo limitato e Ruby non ha limiti su come ottenere numeri grandi.
Ayckoster,

Puoi, perché in realtà non devi ordinare gli elementi - hai solo bisogno di un elemento di mappatura hash -> contare per entrambi gli array, quindi scorrere le chiavi e confrontare i conteggi.
Klochner,

Sei sicuro che Array # sort utilizza l'ordinamento merge?
Nate Symer,

0

La maggior parte delle risposte basate su (a1 - a2) o (a1 e a2) non funzionerebbe se ci fossero elementi duplicati in uno degli array. Sono arrivato qui alla ricerca di un modo per vedere se tutte le lettere di una parola (divise in una matrice) facevano parte di un insieme di lettere (ad esempio per lo scarabeo). Nessuna di queste risposte ha funzionato, ma questa fa:

def contains_all?(a1, a2)
  try = a1.chars.all? do |letter|
    a1.count(letter) <= a2.count(letter)
  end
  return try
end
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.