Matriz inclui algum valor de outra matriz?

155

Qual é a maneira mais eficiente de testar se uma matriz contém algum elemento de uma segunda matriz?

Dois exemplos abaixo, a tentativa de responder à pergunta foodscontém qualquer elemento de 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
Paul Groves
fonte

Respostas:

268
(cheeses & foods).empty?

Como Marc-André Lafortune disse nos comentários, &trabalha em tempo linear enquanto any?+ include?será quadrático. Para conjuntos maiores de dados, o tempo linear será mais rápido. Para conjuntos de dados pequenos, any?+ include?pode ser mais rápido, como mostra a resposta de Lee Jarvis - provavelmente porque &aloca uma nova matriz, enquanto outra solução não funciona e funciona como um loop aninhado simples para retornar um booleano.

Nakilon
fonte
3
Ao verificar se uma matriz contém um elemento de outra matriz, não faria mais sentido fazer (queijos e alimentos). como isso retorna um valor verdadeiro se as matrizes de fato contêm algum dos mesmos elementos?
Ryan Francis
1
@RyanFrancis, docs: any?: O método retorna true se o bloco sempre retorna um valor diferente de false ou nil. empty?: Retorna true se self não contém elementos.
Nakilon
3
@ Nakilon Também estou confuso por que a resposta não (cheeses & foods).any?é a pergunta do OP: se existem alimentos em queijos? No exemplo dele, "feta" está nos dois, então o resultado deve ser verdadeiro, certo? Então, por que verificar .empty?no cruzamento?
SuckerForMayhem 4/16
@SuckerForMayhem, porque a pergunta do OP é "Se houver ... ?", Não apenas "Se houver?". Se " are ... " for omitido, é assumido como "If any for True? " E retornaria False para uma matriz como [false, false, false], embora obviamente não esteja vazio.
Nakilon
Existe alguma implementação no nível do registro ativo?
Lee Chun Hoe
35

Que tal o 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 de 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

Resultado:

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)
Lee Jarvis
fonte
Você pode melhorar isso transformando cheeses- se em um conjunto.
akuhn
1
Executei meu próprio benchmark aqui no ruby ​​2.2.7 e 2.3.4 e any?, include?foi o mais rápido, o conjunto desmembrou o mais lento: gist.github.com/jaredmoody/d2a1e83de2f91fd6865920cd01a8b497
Jared
4
Esta referência é tendenciosa pelo exemplo específico mencionado e não é necessariamente válida em um caso mais geral. E se não houvesse elementos comuns entre as duas matrizes? E se as matrizes estivessem em uma ordem diferente em cada passagem? E se feta apareceu no final de ambas as matrizes? Como Marc-André afirmou, a interseção de conjuntos é executada em tempo linear; portanto, faz sentido que seja muito mais escalável para o caso geral, em vez do exemplo específico usado apenas para esclarecer a questão.
user2259664
22

Você pode verificar se o cruzamento está vazio.

cheeses = %w(chedder stilton brie mozzarella feta haloumi)
foods = %w(pizza feta foods bread biscuits yoghurt bacon)
foods & cheeses
=> ["feta"] 
(foods & cheeses).empty?
=> false
Simone Carletti
fonte
1
Set.new(cheeses).disjoint? Set.new(foods)
davidkovsky
fonte
Também no meu benchmark (não científico), o conjunto disjoint foi significativamente mais lento que outros métodos: gist.github.com/jaredmoody/d2a1e83de2f91fd6865920cd01a8b497
Jared:
1
Obrigado por seus comentários. Não sei por que não foi Set.new, mas apenas editei. Eu tentei seus benchmarks de desempenho na 2.4.1. O meu foi melhor, mas ainda não o melhor, usando conjuntos separados contendo mais palavras. Eu coloquei minha versão em um comentário na sua essência. Eu também acho que disjoint?é muito elegante, especialmente em comparação com "any ?, include?". A pergunta original perguntou sobre elegante e eficiente.
Davidkovsky
.to_setmétodo pode ser útil aquicheeses.to_set.disjoint?(foods.to_set)
itsnikolay 8/08
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)
Ram no Rails-n-React
fonte