Question Vérifiez si une valeur existe dans un tableau dans Ruby


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

Comment est-ce que je vérifie s'il existe dans le tableau sans le boucler? Existe-t-il un moyen simple de vérifier si la valeur existe, rien de plus?


1106
2017-12-31 17:49


origine


Réponses:


Vous cherchez include?:

>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true

1659
2017-12-31 17:51



Il y a un in? méthode dans ActiveSupport (partie de Rails) depuis v3.1, comme indiqué par @campaterson. Donc, dans Rails, ou si vous require 'active_support', tu peux é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 déjà été proposée, en particulier par Yusuke Endoh un membre supérieur de ruby-core.

Comme indiqué par d'autres, la méthode inverse include? existe, pour tous Enumerables 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.-à-d. O(n)), tandis que cette recherche d'un hachage sera un temps constant (c.-à-d. O(1)). Donc, si votre tableau est constant, par exemple, c'est une bonne idée d'utiliser un Ensemble au lieu. 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

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

Une note de clôture finale: méfiez-vous lors de l'utilisation include? sur un Range, il y a des subtilités, alors référez-vous à Le document et comparez avec cover?...


205
2018-05-15 12:50



Essayer

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

157
2017-12-31 17:52



Utilisation 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 (même include? a) et aller de Sur) à O (1) avec:

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


1. J'espère que c'est évident mais pour éviter les objections: oui, pour juste quelques recherches, le Hash [] et le transpose ops dominent le profil et sont chacun Sur) se.


44
2017-12-31 17:52



Si vous voulez vérifier par un bloc, vous pouvez en essayer un? ou tout?

%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


41
2018-05-20 09:08



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 une 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 boucler est en construisant un Trie pour votre tableau. Il existe de nombreuses implémentations trie (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 divers mots dans votre tableau sans boucler dessus, dans O(log n) temps, avec la même simplicité syntaxique Array#include?, en utilisant le sous-linéaire Trie#include?:

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

28
2018-06-10 16:23



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 et ensuite 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 retournent un trueish value si l'élément est présent.

include? est la méthode préférée. Il utilise un langage C for boucle interne qui casse quand un élément correspond à l'interne rb_equal_opt/rb_equalles fonctions. Il ne peut pas être beaucoup plus efficace à moins de créer un ensemble pour les contrôles d'appartenance 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 Array classe et utilise une implémentation non optimisée de la Enumerable module qui énumère 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 cela fait à propos de ce qui suit

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

Tous les deux include? et member? avoir O(n) complexité temporelle puisque les deux recherchent dans le tableau la première occurrence de la valeur attendue.

Nous pouvons utiliser un ensemble pour obtenir O(1) temps d'accès au prix d'avoir à créer une représentation de hachage du tableau en premier. Si vous vérifiez à plusieurs reprises l'appartenance à la même matrice, cet investissement initial peut être rentable rapidement. Set n'est pas implémenté en C mais en tant que classe simple Ruby, toujours le O(1) temps d'accès du sous-jacent @hash Cela en vaut la peine.

Voici la mise en œuvre de la Set classe,

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 Set classe crée juste un interne @hash instance, mappe tous les objets sur true puis vérifie l'adhésion en utilisant Hash#include? qui est mis en œuvre avec O(1) temps d'accès dans le Hash 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 O(n) complexité au-delà des 11 énumérés ci-dessus, mais j'ai décidé de ne pas les énumérer depuis balayer l'ensemble du tableau plutôt que de rompre au premier match.

Ne les utilisez pas,

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

23
2017-12-25 23:40