Le tableau contient-il une valeur d'un autre tableau?


155

Quel est le moyen le plus efficace de tester si un tableau contient un élément d'un deuxième tableau?

Deux exemples ci-dessous, qui tentent de répondre à la question, foodscontiennent un élément 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

Réponses:


268
(cheeses & foods).empty?

Comme le disait Marc-André Lafortune dans ses commentaires, &travaille en temps linéaire tandis que any?+ include?sera quadratique. Pour des ensembles de données plus volumineux, le temps linéaire sera plus rapide. Pour les petits ensembles de données, any?+ include?peut être plus rapide comme le montre la réponse de Lee Jarvis - probablement parce qu'il &alloue un nouveau tableau alors qu'une autre solution ne le fait pas et fonctionne comme une simple boucle imbriquée pour renvoyer un booléen.


3
Lorsque vous vérifiez si un tableau contient un élément d'un autre tableau, ne serait-il pas plus judicieux de faire (fromages et aliments). car cela renvoie une valeur vraie si les tableaux contiennent en fait l'un des mêmes éléments?
Ryan Francis

1
@RyanFrancis, docs: any?: La méthode renvoie true si le bloc retourne toujours une valeur autre que fausse ou nulle. empty?: Renvoie vrai si self ne contient aucun élément.
Nakilon

3
@Nakilon Je ne comprends pas (cheeses & foods).any?non plus pourquoi la réponse n'est pas la question du PO: s'il y a des aliments dans les fromages? Dans son exemple, "feta" est dans les deux, donc le résultat devrait être vrai, non? Alors pourquoi vérifier .empty?l'intersection?
SuckerForMayhem

@SuckerForMayhem, parce que la question d'OP est "Si il y en a ... ?", Pas seulement "Si oui?". Si " are ... " est omis, il est supposé être "If any is True? " Et retournerait False pour un tableau comme [false, false, false], alors qu'il n'est évidemment pas vide.
Nakilon

Y a-t-il une implémentation au niveau activerecord?
Lee Chun Hoe

35

Que diriez-vous de 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 référence:

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

Résultat:

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)

Vous pouvez améliorer cela en le transformant cheesesen un ensemble.
akuhn

1
J'ai couru mon propre benchmark ici sur ruby ​​2.2.7 et 2.3.4 et any?, include?était le plus rapide, disjoint le plus lent: gist.github.com/jaredmoody/d2a1e83de2f91fd6865920cd01a8b497
Jared

4
Ce point de repère est biaisé par l'exemple spécifique mentionné et ne tient pas nécessairement dans un cas plus général. Et s'il n'y avait pas d'éléments communs entre les deux tableaux? Et si les tableaux étaient dans un ordre différent à chaque passage? Et si la feta apparaissait à la fin des deux tableaux? Comme Marc-André l'a déclaré, l'intersection d'ensemble s'exécute en temps linéaire, il est donc logique qu'elle soit beaucoup plus évolutive pour le cas général, plutôt que le seul exemple spécifique utilisé uniquement pour clarifier la question.
user2259664

22

Vous pouvez vérifier si l'intersection est vide.

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)

Également dans mon benchmark (non scientifique), set disjoint était significativement plus lent que les autres méthodes: gist.github.com/jaredmoody/d2a1e83de2f91fd6865920cd01a8b497
Jared

1
Merci pour vos commentaires. Je ne sais pas pourquoi ce n'était pas Set.new mais je l'ai juste édité. J'ai essayé vos benchmarks de performance en 2.4.1. Le mien a fait mieux mais pas encore mieux en utilisant des ensembles disjoints contenant plus de mots. J'ai mis ma version dans un commentaire sur ton essence. Je pense aussi que disjoint?c'est très élégant, surtout par rapport à "any ?, include?". La question initiale posait à la fois l'élégance et l'efficacité.
davidkovsky

.to_setméthode peut être utile icicheeses.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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.