L'array include qualsiasi valore da un altro array?


155

Qual è il modo più efficiente per verificare se un array contiene elementi di un secondo array?

Due esempi di seguito, il tentativo di rispondere alla domanda foodscontiene elementi da cheeses:

cheeses = %w(chedder stilton brie mozzarella feta haloumi reblochon)
foods = %w(pizza feta foods bread biscuits yoghurt bacon)

puts cheeses.collect{|c| foods.include?(c)}.include?(true)

puts (cheeses - foods).size < cheeses.size

Risposte:


268
(cheeses & foods).empty?

Come ha detto Marc-André Lafortune nei commenti, &funziona in tempo lineare mentre any?+ include?sarà quadratico. Per gruppi di dati più grandi, il tempo lineare sarà più veloce. Per piccoli insiemi di dati, any?+ include?potrebbe essere più veloce, come mostrato dalla risposta di Lee Jarvis, probabilmente perché &alloca un nuovo array mentre un'altra soluzione non funziona e funziona come un semplice ciclo nidificato per restituire un valore booleano.


3
Quando si controlla se un array contiene un elemento di un altro array, non avrebbe più senso fare (formaggi e alimenti). come questo restituisce un valore vero se gli array contengono effettivamente uno degli stessi elementi?
Ryan Francis,

1
@RyanFrancis, documenti: any?: Il metodo restituisce true se il blocco mai restituisce un valore diverso da falso o nullo. empty?: Restituisce true se self non contiene elementi.
Nakilon,

3
@Nakilon Sono anche confuso perché la risposta non (cheeses & foods).any?è la domanda dell'OP: se ci sono alimenti nei formaggi? Nel suo esempio, "feta" è in entrambi, quindi il risultato dovrebbe essere vero, giusto? Quindi perché controllare .empty?l'incrocio?
SuckerForMayhem

@SuckerForMayhem, perché la domanda di OP è "Se ce ne sono ... ?", Non solo "Se ce ne sono?". Se " are ... " viene omesso, si presume che sia "If any is True? " E restituisce False per array come [false, false, false], mentre ovviamente non è vuoto.
Nakilon,

C'è qualche implementazione a livello di activerecord?
Lee Chun Hoe,

35

Che ne dici di Enumerable # any?

>> cheeses = %w(chedder stilton brie mozzarella feta haloumi)
=> ["chedder", "stilton", "brie", "mozzarella", "feta", "haloumi"]
>> foods = %w(pizza feta foods bread biscuits yoghurt bacon)
=> ["pizza", "feta", "foods", "bread", "biscuits", "yoghurt", "bacon"]
>> foods.any? {|food| cheeses.include?(food) }
=> true

Script di benchmark:

require "benchmark"
N = 1_000_000
puts "ruby version: #{RUBY_VERSION}"

CHEESES = %w(chedder stilton brie mozzarella feta haloumi).freeze
FOODS = %w(pizza feta foods bread biscuits yoghurt bacon).freeze

Benchmark.bm(15) do |b|
  b.report("&, empty?") { N.times { (FOODS & CHEESES).empty? } }
  b.report("any?, include?") { N.times { FOODS.any? {|food| CHEESES.include?(food) } } }
end

Risultato:

ruby version: 2.1.9
                      user     system      total        real
&, empty?         1.170000   0.000000   1.170000 (  1.172507)
any?, include?    0.660000   0.000000   0.660000 (  0.666015)

Puoi migliorare questo trasformandolo cheesesin un set.
Akuhn,

1
Ho guidato il mio benchmark qui su Ruby 2.2.7 e 2.3.4 ed è any?, include?stato il più veloce, set disgiunto il più lento: gist.github.com/jaredmoody/d2a1e83de2f91fd6865920cd01a8b497
Jared

4
Questo benchmark è distorto dall'esempio specifico citato e non vale necessariamente in un caso più generale. E se non ci fossero elementi comuni tra i due array? E se gli array fossero in un ordine diverso ad ogni passaggio? E se la feta apparisse alla fine di entrambi gli array? Come ha affermato Marc-André, set intersezione viene eseguita in un tempo lineare, quindi ha senso che sia molto più scalabile per il caso generale, piuttosto che un esempio specifico usato puramente per chiarire la domanda.
user2259664

22

È possibile verificare se l'intersezione è vuota.

cheeses = %w(chedder stilton brie mozzarella feta haloumi)
foods = %w(pizza feta foods bread biscuits yoghurt bacon)
foods & cheeses
=> ["feta"] 
(foods & cheeses).empty?
=> false

1
Set.new(cheeses).disjoint? Set.new(foods)

Anche nel mio benchmark (non scientifico), impostare disjoint era significativamente più lento rispetto ad altri metodi: gist.github.com/jaredmoody/d2a1e83de2f91fd6865920cd01a8b497
Jared,

1
Grazie per i tuoi commenti Non sono sicuro del perché non fosse Set.new ma l'ho appena modificato. Ho provato i tuoi benchmark delle prestazioni in 2.4.1. Il mio ha fatto meglio ma ancora non è meglio usare insiemi sconnessi contenenti più parole. Ho inserito la mia versione in un commento sul tuo contenuto. Penso anche che disjoint?sia molto elegante, soprattutto rispetto a "any ?, include?". La domanda originale era sia elegante che efficiente.
davidkovsky,

.to_setil metodo può essere utile quicheeses.to_set.disjoint?(foods.to_set)
itsnikolay

0
require "benchmark"
N = 1_000_000
puts "ruby version: #{RUBY_VERSION}"

CHEESES = %w(chedder stilton brie mozzarella feta haloumi).freeze
FOODS = %w(pizza feta foods bread biscuits yoghurt bacon).freeze

Benchmark.bm(15) do |b|
  b.report("&, empty?") { N.times { (FOODS & CHEESES).empty? } }  
  b.report("any?, include?") { N.times { FOODS.any? {|food| CHEESES.include?(food) } } }  
  b.report("disjoint?") { N.times { FOODS.to_set.disjoint? CHEESES.to_set }}
end  
                      user     system      total        real
&, empty?         0.751068   0.000571   0.751639 (  0.752745)
any?, include?    0.408251   0.000133   0.408384 (  0.408438)
disjoint?        11.616006   0.014806  11.630812 ( 11.637300)
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.