web-dev-qa-db-fra.com

Vérifier si une valeur existe dans un tableau dans Ruby

J'ai une valeur 'Dog' et un tableau ['Cat', 'Dog', 'Bird'].

Comment vérifier s'il existe dans le tableau sans le parcourir en boucle? Existe-t-il un moyen simple de vérifier si la valeur existe, rien de plus?

1227
user211662

Vous recherchez include? :

_>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
_
1834
Brian Campbell

Il existe une méthode in? dans ActiveSupport (élément de Rails) depuis la version 3.1, comme l'a souligné @campaterson. Donc, dans Rails, ou si vous _require 'active_support'_, vous pouvez écrire:

_'Unicorn'.in?(['Cat', 'Dog', 'Bird']) # => false
_

OTOH, il n'y a pas de méthode in ou _#in?_ dans Ruby lui-même, même si elle a déjà été proposée auparavant, en particulier par Yusuke Endoh en haut membre encoche de Ruby-core.

Comme l'ont souligné d'autres personnes, la méthode inverse include? existe pour toutes les Enumerable, y compris Array, Hash, Set, Range:

_['Cat', 'Dog', 'Bird'].include?('Unicorn') # => false
_

Notez que si vous avez plusieurs valeurs dans votre tableau, elles seront toutes vérifiées les unes après les autres (c'est-à-dire O(n)), tandis que la recherche d'un hachage sera à temps constant (c'est-à-dire O(1)). Donc si votre tableau est constant, par exemple, c'est une bonne idée d'utiliser plutôt Set . Par exemple:

_require 'set'
ALLOWED_METHODS = Set[:to_s, :to_i, :upcase, :downcase
                       # etc
                     ]

def foo(what)
  raise "Not allowed" unless ALLOWED_METHODS.include?(what.to_sym)
  bar.send(what)
end
_

Un test rapide révèle que l'appel de _include?_ sur un 10 élément Set est environ 3,5 fois plus rapide que de l'appeler sur son équivalent Array (si l'élément n'est pas trouvé ).

Une dernière note de clôture: soyez prudent lorsque vous utilisez _include?_ sur un Range, il y a des subtilités, alors reportez-vous à la doc et comparez-le avec cover? ...

234

Essayer

['Cat', 'Dog', 'Bird'].include?('Dog')
161
schmitzelburger

Utilisez Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

Ou, si un certain nombre de tests sont effectués,1 vous pouvez vous débarrasser de la boucle (que même include? a) et aller de O (n) à O (1) avec:

h = Hash[[a, a].transpose]
h['Dog']

1. J'espère que cela est évident, mais pour éviter les objections: oui, pour quelques recherches, les opérations de hachage [] et de transposition dominent le profil et sont chacun O (n) eux-mêmes.
48
DigitalRoss

Si vous voulez vérifier par un bloc, vous pouvez essayer? ou tous?.

%w{ant bear cat}.any? {|Word| Word.length >= 3}   #=> true  
%w{ant bear cat}.any? {|Word| Word.length >= 4}   #=> true  
[ nil, true, 99 ].any?                            #=> true  

Les détails sont ici: http://Ruby-doc.org/core-1.9.3/Enumerable.html
Mon inspiration vient d’ici: https://stackoverflow.com/a/10342734/576497

44
Van

Ruby a 11 méthodes pour trouver des éléments dans un tableau.

Le préféré est include?

Ou, pour un accès répété, créer un ensemble puis appeler include? ou member?

Voici tous,

array.include?(element) # preferred method
array.member?(element)
array.to_set.include?(element)
array.to_set.member?(element)
array.index(element) > 0
array.find_index(element) > 0
array.index { |each| each == element } > 0
array.find_index { |each| each == element } > 0
array.any? { |each| each == element }
array.find { |each| each == element } != nil
array.detect { |each| each == element } != nil

Tous renvoient une valeur trueish si l'élément est présent.

include? est la méthode préférée. Il utilise une boucle for de langage C en interne qui se déclenche lorsqu'un élément correspond aux fonctions internes rb_equal_opt/rb_equal. Cela ne peut être beaucoup plus efficace si vous ne créez pas un ensemble pour les contrôles d'adhésion répétés.

VALUE
rb_ary_includes(VALUE ary, VALUE item)
{
  long i;
  VALUE e;

  for (i=0; i<RARRAY_LEN(ary); i++) {
    e = RARRAY_AREF(ary, i);
    switch (rb_equal_opt(e, item)) {
      case Qundef:
        if (rb_equal(e, item)) return Qtrue;
        break;
      case Qtrue:
        return Qtrue;
    }
  }
  return Qfalse;
}

member? n'est pas redéfini dans la classe Array et utilise une implémentation non optimisée du module Enumerable qui énumère littéralement tous les éléments.

static VALUE
member_i(RB_BLOCK_CALL_FUNC_ARGLIST(iter, args))
{
  struct MEMO *memo = MEMO_CAST(args);

  if (rb_equal(rb_enum_values_pack(argc, argv), memo->v1)) {
    MEMO_V2_SET(memo, Qtrue);
    rb_iter_break();
  }
  return Qnil;
}

static VALUE
enum_member(VALUE obj, VALUE val)
{
  struct MEMO *memo = MEMO_NEW(val, Qfalse, 0);

  rb_block_call(obj, id_each, 0, 0, member_i, (VALUE)memo);
  return memo->v2;
}

Traduit en Ruby code, ce qui concerne les éléments suivants

def member?(value)
  memo = [value, false, 0]
  each_with_object(memo) do |each, memo|
    if each == memo[0]
      memo[1] = true 
      break
    end
  memo[1]
end

include? et member? ont tous deux une complexité temporelle de O(n) dans la mesure où ils effectuent une recherche dans le tableau à la première occurrence de la valeur attendue.

Nous pouvons utiliser un ensemble pour obtenir le temps d'accès O(1) au prix de la création préalable d'une représentation hachée du tableau. Si vous vérifiez de manière répétée l'appartenance au même groupe, cet investissement initial peut rapporter rapidement. Set n'est pas implémenté en C mais en tant que classe Ruby simple, le temps d'accès O(1) du @hash sous-jacent en vaut toutefois la peine.

Voici l'implémentation de la classe Set,

module Enumerable
  def to_set(klass = Set, *args, &block)
    klass.new(self, *args, &block)
  end
end

class Set
  def initialize(enum = nil, &block) # :yields: o
    @hash ||= Hash.new
    enum.nil? and return
    if block
      do_with_enum(enum) { |o| add(block[o]) }
    else
      merge(enum)
    end
  end

  def merge(enum)
    if enum.instance_of?(self.class)
      @hash.update(enum.instance_variable_get(:@hash))
    else
      do_with_enum(enum) { |o| add(o) }
    end
    self
  end

  def add(o)
    @hash[o] = true
    self
  end

  def include?(o)
    @hash.include?(o)
  end
  alias member? include?

  ...
end

Comme vous pouvez le constater, la classe Set crée simplement une instance interne @hash, mappe tous les objets sur true, puis vérifie l'appartenance à l'aide de Hash#include?, qui est implémenté avec le temps d'accès O(1) dans Hash classe.

Je ne discuterai pas des 7 autres méthodes car elles sont toutes moins efficaces.

Il existe en fait encore plus de méthodes avec la complexité de O(n) au-delà des 11 énumérées ci-dessus, mais j'ai décidé de ne pas les énumérer, car elles analysent l'ensemble du tableau plutôt que de se rompre à la première correspondance.

Ne les utilisez pas

# bad examples
array.grep(element).any? 
array.select { |each| each == element }.size > 0
...
32
akuhn

Plusieurs réponses suggèrent Array#include?, mais il y a une mise en garde importante: si on regarde la source, même Array#include? exécute la mise en boucle:

rb_ary_includes(VALUE ary, VALUE item)
{
    long i;

    for (i=0; i<RARRAY_LEN(ary); i++) {
        if (rb_equal(RARRAY_AREF(ary, i), item)) {
            return Qtrue;
        }
    }
    return Qfalse;
}

Le moyen de tester la présence de Word sans bouclage consiste à construire un trie pour votre tableau. Il existe de nombreuses implémentations de trie (google "Ruby trie"). Je vais utiliser rambling-trie dans cet exemple:

a = %w/cat dog bird/

require 'rambling-trie' # if necessary, gem install rambling-trie
trie = Rambling::Trie.create { |trie| a.each do |e| trie << e end }

Et maintenant, nous sommes prêts à tester la présence de divers mots dans votre tableau sans effectuer de boucle sur celui-ci, dans O(log n) time, avec la même simplicité syntaxique que Array#include?, en utilisant le sous-linéaire Trie#include?:

trie.include? 'bird' #=> true
trie.include? 'duck' #=> false
29
Boris Stitnicky

Si vous ne voulez pas faire de boucle, il n'y a aucun moyen de le faire avec Arrays. Vous devriez utiliser un ensemble à la place.

require 'set'
s = Set.new
100.times{|i| s << "foo#{i}"}
s.include?("foo99")
 => true
[1,2,3,4,5,6,7,8].to_set.include?(4) 
  => true

Les ensembles fonctionnent en interne comme des hachages. Ainsi, Ruby n'a pas besoin de parcourir la collection pour rechercher des éléments, car, comme son nom l'indique, il génère des hachages des clés et crée un mappage de mémoire de sorte que chaque point de hachage un certain point en mémoire. L'exemple précédent réalisé avec un hachage:

fake_array = {}
100.times{|i| fake_array["foo#{i}"] = 1}
fake_array.has_key?("foo99")
  => true

L'inconvénient est que les ensembles et les clés de hachage ne peuvent inclure que des éléments uniques. Si vous ajoutez de nombreux éléments, Ruby devra réorganiser l'ensemble après un certain nombre d'éléments pour créer une nouvelle carte plus grande. espace de clés. Pour plus d'informations à ce sujet, je vous recommande de regarder MountainWest RubyConf 2014 - Big O dans un hachage fait maison de Nathan Long

Voici un repère:

require 'benchmark'
require 'set'

array = []
set   = Set.new

10_000.times do |i|
  array << "foo#{i}"
  set   << "foo#{i}"
end

Benchmark.bm do |x|
  x.report("array") { 10_000.times { array.include?("foo9999") } }
  x.report("set  ") { 10_000.times { set.include?("foo9999")   } }
end

Et les résultats:

      user     system      total        real
array  7.020000   0.000000   7.020000 (  7.031525)
set    0.010000   0.000000   0.010000 (  0.004816)
16
Kimmo Lehto

C'est une autre façon de faire: utilisez la méthode index Array #.

Il retourne l'index de la première occurrence de l'élément dans le tableau.

exemple:

a = ['cat','dog','horse']
if a.index('dog')
    puts "dog exists in the array"
end

index () peut aussi prendre un bloc

par exemple

a = ['cat','dog','horse']
puts a.index {|x| x.match /o/}

ici, retourne l'index du premier mot du tableau contenant la lettre "o".

15
Zack Xu

Fait amusant,

Vous pouvez utiliser * pour vérifier l'appartenance à un tableau dans une expression case.

case element
when *array 
  ...
else
  ...
end

Notez le petit * dans la clause when, ceci vérifie l'appartenance au tableau.

Tout le comportement magique habituel de l'opérateur splat s'applique. Ainsi, par exemple, si array n'est pas réellement un tableau mais un seul élément, il correspond à cet élément.

9
akuhn

Il y a plusieurs façons d'accomplir cela. Quelques-uns d'entre eux sont les suivants:

a = [1,2,3,4,5]

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false
8
sumit

Cela vous dira non seulement qu'il existe, mais aussi combien de fois il apparaît:

 a = ['Cat', 'Dog', 'Bird']
 a.count("Dog")
 #=> 1
5
user3245240

Si vous devez vérifier plusieurs fois une clé, convertissez arr en hash, et enregistrez à présent O (1).

arr = ['Cat', 'Dog', 'Bird']
hash = arr.map {|x| [x,true]}.to_h
 => {"Cat"=>true, "Dog"=>true, "Bird"=>true}
hash["Dog"]
 => true
hash["Insect"]
 => false

Performances de Hash # has_key? versus Array # include?

 Parameter Hash # has_key? Tableau # comprend 
 
 Complexité temporelle O(1) opération O(n) opération 
 
 Type d'accès Accès Hash [clé] s'il parcourt chaque élément 
 Renvoie n'importe quelle valeur du tableau jusqu'à ce que 
 True soit renvoyé à la valeur trouvée dans Array 
 Hash # has_key? appeler 
 appeler 

Pour un contrôle ponctuel, utilisez include?, c'est bien

4
aqfaridi

Si vous avez plus de valeurs en tête ... vous pouvez essayer:

Exemple: si Cat et Dog existent dans le tableau:

(['Cat','Dog','Bird'] & ['Cat','Dog'] ).size == 2   #or replace 2 with ['Cat','Dog].size

Au lieu de:

['Cat','Dog','Bird'].member?('Cat') and ['Cat','Dog','Bird'].include?('Dog')

Note: membre? et inclure? sont identiques.

Cela peut faire le travail en une ligne!

Pour ce que ça vaut, les ruby docs sont une ressource incroyable pour ce genre de questions.

Je voudrais également prendre note de la longueur du tableau que vous recherchez. La méthode include? exécute une recherche linéaire avec une complexité de O(n) qui peut devenir assez moche en fonction de la taille du tableau.

Si vous travaillez avec un grand tableau (trié), je penserais à écrire un algorithme de recherche binaire qui ne devrait pas être trop difficile et qui a le pire cas de O (log n).

Ou si vous utilisez Ruby 2.0, vous pouvez tirer parti de bsearch.

4
davissp14

Si nous ne voulons pas utiliser include? cela fonctionne aussi:

['cat','dog','horse'].select{ |x| x == 'dog' }.any?
3
xlembouras

Il y a aussi l'inverse!

Supposons que le tableau soit [: edit,: update,: create,: show] - enfin, peut-être l’ensemble sept péchés capitaux/reposants :)

Et plus jouet avec l'idée de tirant une action valide d'une chaîne - dire

mon frère aimerait que je mette à jour son profil

Solution

[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}
3
walt_die
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
3
Rahul Patel

Si vous essayez de le faire dans un test unitaire MiniTest , vous pouvez utiliser assert_includes . Exemple:

_pets = ['Cat', 'Dog', 'Bird']
assert_includes(pets, 'Dog')      # -> passes
assert_includes(pets, 'Zebra')    # -> fails 
_
2
Jon Schneider

Que diriez-vous de cette façon?

['Cat', 'Dog', 'Bird'].index('Dog')
2
ajahongir

Si vous voulez renvoyer la valeur non seulement true ou false, utilisez

array.find{|x| x == 'Dog'}

Cela retournera 'Chien' s'il existe dans la liste, sinon nil.

1
gitb

si vous ne voulez pas utiliser include? vous pouvez d'abord envelopper l'élément dans un tableau, puis vérifier si l'élément enveloppé est égal à l'intersection du tableau et de l'élément enveloppé. Cela retournera une valeur booléenne basée sur l'égalité.

def in_array?(array, item)
    item = [item] unless item.is_a?(Array)
    item == array & item
end
0
mgidea

Voici une autre façon de faire ceci:

arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'

present = arr.size != (arr - [e]).size
0
Wand Maker
array = [ 'Cat', 'Dog', 'Bird' ]
array.include?("Dog")
0
Matthew Maurice