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

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

Comment puis-je vérifier si elle existe dans le tableau sans la boucler? Est-il un moyen simple de vérifier si la valeur existe, rien de plus?

1125
demandé sur the Tin Man 2009-12-31 20:49:03

22 réponses

vous recherchez include? :

>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
1687
répondu Brian Campbell 2016-04-29 12:02:04

il y a une in? méthode dans ActiveSupport (partie de Rails) depuis v3.1, comme l'a souligné @campaterson. Donc dans les Rails, ou si vous require 'active_support' , vous pouvez écrire:

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

OTOH, il n'y a pas in opérateur ou #in? méthode dans Ruby lui-même, même si elle a été proposée avant, en particulier par Yusuke Endoh un membre de haut niveau de Ruby-core.

As fait remarquer par d'autres, la méthode inverse include? existe, pour tous les Enumerable s, y compris Array , Hash , Set , Range :

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

notez que si vous avez beaucoup de valeurs dans votre tableau, elles seront toutes vérifiées l'une après l'autre (i.e. O(n) ), alors que cette recherche pour un hash sera un temps constant (I. e O(1) ). Donc si votre tableau est constant, par exemple, il est une bonne idée d'utiliser un Set à la place. 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

A quick test révèle que l'appel include? sur un 10 élément Set est environ 3,5 fois plus rapide que l'appel sur l'équivalent Array (si l'élément n'est pas trouvé).

Une clôture finale remarque: méfiez-vous lors de l'utilisation de include? sur un Range , il y a des subtilités, reportez-vous à le doc et comparer avec cover? ...

209
répondu Marc-André Lafortune 2017-08-10 20:27:27

Essayer

['Cat', 'Dog', 'Bird'].include?('Dog')
157
répondu schmitzelburger 2009-12-31 17:52:28

utiliser 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 les include? a) et de passer de la O(n) à O(1) :

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


1. J'espère que c'est évident, mais pour éviter les objections: Oui, pour quelques recherches, le Hash[] et transposer ops dominent le profil et sont chacun O (n) eux-mêmes.
44
répondu DigitalRoss 2016-02-06 00:18:29

Si vous voulez vérifier par un bloc, vous pouvez essayer de tout? 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  

détails ici: http://ruby-doc.org/core-1.9.3/Enumerable.html

Mon inspiration vient d'ici: https://stackoverflow.com/a/10342734/576497

42
répondu Van 2017-05-23 12:10:48

plusieurs réponses suggèrent Array#include? , mais il y a une mise en garde importante: en regardant la source, même Array#include? effectue la 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;
}

la façon de tester le mot présence sans boucle est en construisant un trie pour votre tableau. Il existe de nombreuses implémentations trie là-bas (google "ruby trie"). J'utiliserai 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 différents mots dans votre tableau sans boucle au-dessus de lui, dans O(log n) temps, avec la même simplicité syntaxique que Array#include? , en utilisant sublinéaire Trie#include? :

trie.include? 'bird' #=> true
trie.include? 'duck' #=> false
27
répondu Boris Stitnicky 2013-06-10 16:23:39

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

le préféré est include?

ou pour accès répété, créant un ensemble et appelant ensuite include? ou member?

Ici, ils le sont 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

toutes renvoient une valeur ish true si l'élément est présent.

include? est la méthode préférée. Il utilise un C-language for boucle interne qui se casse lorsqu'un élément correspond aux fonctions internes rb_equal_opt/rb_equal . Il ne peut pas être beaucoup plus efficace à moins que vous créez un ensemble pour les vérifications d'adhésion répétées.

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èrent littéralement à travers 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 code Ruby fait à propos de la suivante

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

les deux include? et member? ont O(n) complexité de temps depuis les deux rechercher le tableau pour la première occurrence de la valeur attendue.

nous pouvons utiliser un ensemble pour obtenir O(1) temps d'accès au coût de devoir créer une représentation de hachage du tableau d'abord. Si vous vérifiez à plusieurs reprises l'adhésion sur le même tableau cet investissement initial peut payer rapidement. Set n'est pas mis en œuvre en C mais en tant que Classe Ruby simple, le temps d'accès O(1) du sous-jacent @hash rend cela valable.

Voici la mise en œuvre 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 voir la classe Set crée juste une instance interne @hash , mappe tous les objets à true et vérifie ensuite l'adhésion en utilisant Hash#include? qui est mis en œuvre avec O(1) temps d'accès dans le Hash de la classe.

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

il y a en fait encore plus de méthodes avec la complexité O(n) au-delà des 11 énumérées ci-dessus, mais j'ai décidé de ne pas les énumérer car je scanne l'ensemble du tableau plutôt que de les casser lors du premier match.

de Ne pas utiliser ces,

# bad examples
array.grep(element).any? 
array.select { |each| each == element }.size > 0
...
24
répondu akuhn 2016-12-25 23:48:10

si vous ne voulez pas boucler, il n'y a aucun moyen de le faire avec des tableaux. 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 hachures, de sorte que Ruby n'a pas besoin de boucler la collection pour trouver des articles, puisque comme le nom l'indique, il génère des hachures des clés et crée une carte mémoire de sorte que chaque point de hachage à un certain point dans la mémoire. L'exemple précédent fait 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 et si vous ajoutez beaucoup d'éléments, Ruby devra ressasser l'ensemble après un certain nombre d'éléments pour construire une nouvelle carte qui convient à un plus grand espace de clé. Pour plus d'informations à ce sujet, je vous recommande de regarder MountainWest RubyConf 2014-Big O in a Homemade Hash de Nathan Long

voici un benchmark:

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
répondu Kimmo Lehto 2014-05-29 19:58:44

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

Il renvoie 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 également prendre un bloc

par exemple

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

ici, renvoie l'index du premier mot dans le tableau contenant la lettre "o".

15
répondu Zack Xu 2013-10-02 17:22:21

Il y a plusieurs façons d'y parvenir. En voici quelques-uns:

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

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false
8
répondu sumit 2016-07-01 15:36:23

fait Amusant,

vous pouvez utiliser * pour vérifier l'appartenance à un tableau dans les expressions case .

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

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

tous les comportements magiques habituels de l'opérateur splat s'appliquent, donc par exemple si array n'est pas en fait un tableau mais un seul élément il correspondra à cet élément.

7
répondu akuhn 2016-12-25 23:48:46

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
répondu user3245240 2014-04-15 18:29:23

pour ce que ça vaut, le Ruby docs est une ressource incroyable pour ce genre de questions.

je voudrais aussi prendre note de la longueur du tableau que vous cherchez à travers. La méthode include? exécutera une recherche linéaire avec O(n) complexité qui peut devenir assez laid selon la taille du tableau.

si vous travaillez avec un grand tableau (trié), j'envisagerais d'écrire une recherche binaire algorithme qui ne devrait pas être trop difficile et a un pire cas de O(log n).

ou si vous utilisez Ruby 2.0, vous pouvez profiter de bsearch .

5
répondu davissp14 2016-12-02 20:47:49

Si vous avez à l'esprit plus de valeurs... vous pouvez essayer:

exemple: si le Chat et le chien 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 de les inclure? sont les mêmes.

cela peut faire le travail en une seule ligne!

4
répondu Daniel Antonio Nuñez Carhuayo 2014-04-07 15:04:57

il y a l'autre chemin, aussi!

supposons que le tableau est [: edit,: update,: create,: show ] - Eh bien peut-être la totalité sept péchés mortels / reposants :)

et jouet supplémentaire avec l'idée de tirant une action valide d'une certaine chaîne-dire

mon frère voudrait 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
répondu walt_die 2012-10-15 14:08:53

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

['cat','dog','horse'].select{ |x| x == 'dog' }.any?
3
répondu xlembouras 2014-02-20 07:24:06

si vous avez besoin de vérifier des temps multiples pour n'importe quelle clé, convertissez arr en hash , et maintenant cochez 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

Performance de Has_key? versus tableau # include?

Parameter              Hash#has_key?                 Array#include 

Time Complexity         O(1) operation                O(n) operation 

Access Type             Accesses Hash[key] if it      Iterates through each element
                        returns any value then        of the array till it
                        true is returned to the       finds the value in Array
                        Hash#has_key? call
                        call    

pour contrôle à temps unique en utilisant include? is fine

3
répondu aqfaridi 2018-09-08 16:22:20

par ici?

['Cat', 'Dog', 'Bird'].index('Dog')
2
répondu ajahongir 2015-01-25 11:28:30
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
2
répondu Rahul Patel 2016-12-19 08:01:54

si vous ne voulez pas l'utiliser? vous pouvez d'abord envelopper l'élément dans un tableau et ensuite vérifier si le enveloppés élément est égal à l'intersection de la matrice et la enveloppé élément. Cela renvoie 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
répondu mgidea 2014-05-15 06:46:49

Voici une autre façon de le faire:

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

present = arr.size != (arr - [e]).size
0
répondu Wand Maker 2016-01-05 07:04:34
array = [ 'Cat', 'Dog', 'Bird' ]
array.include?("Dog")
0
répondu Matthew Maurice 2016-11-29 04:23:41