Verificar se existe um valor numa lista em Ruby

eu tenho um valor 'Dog' e um array ['Cat', 'Dog', 'Bird'].

Como posso verificar se existe na matriz sem passar por ela? Existe uma maneira simples de verificar se o valor existe, nada mais?

 1125
Author: the Tin Man, 2009-12-31

22 answers

Estás à procura include?:
>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
 1687
Author: Brian Campbell, 2016-04-29 12:02:04

Existe um in? Método em ActiveSupport (parte dos carris) desde v3.1, como indicado por @campaterson. Então dentro dos trilhos, ou se você require 'active_support', você pode escrever:

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

OTOH, não há nenhum operador ou método #in? em Ruby, mesmo que tenha sido proposto antes, em particular por Yusuke Endohum membro de topo do ruby-core.

Como outros salientaram, o método inverso include? existe, para todos os Enumerable incluindo Array, Hash, Set, Range:
['Cat', 'Dog', 'Bird'].include?('Unicorn') # => false

Lembre-se que, se tiver muitos valores na sua matriz, todos eles serão verificados um após o outro (isto é O(n)), enquanto que a procura de uma barra será de tempo constante (i. e O(1)). Então, se você array é constante, por exemplo, é uma boa idéia usar um conjunto em vez disso. E. g:

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 teste rápido revela que chamar include? num elemento 10 Set é cerca de 3,5 x mais rápido do que chamá-lo no equivalente Array (Se o elemento não for encontrado).

Uma nota final: tenha cuidado ao usar include? num Range, existem subtilezas, por isso consulte o doc e compare com cover?...

 209
Author: Marc-André Lafortune, 2017-08-10 20:27:27

Tenta

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

Utilizar Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

Ou, se for efectuado um certo número de testes,1 Você pode se livrar do loop (que até include? tem) e ir de o(n) a O(1) com:

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


1. Espero que isso seja óbvio, mas para evitar objeções: sim, para apenas algumas pesquisas, o Hash[] e transpose ops dominam o perfil e são cada um O(n).
 44
Author: DigitalRoss, 2016-02-06 00:18:29
Se quiser verificar por um quarteirão, pode tentar qualquer? ou tudo?.
%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  

Os detalhes estão aqui: http://ruby-doc.org/core-1.9.3/Enumerable.html
A minha inspiração vem daqui. https://stackoverflow.com/a/10342734/576497

 42
Author: Van, 2017-05-23 12:10:48

Várias respostas sugerem Array#include?, mas há uma advertência importante: olhando para a fonte, mesmo {[3] } faz looping:

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;
}

A maneira de testar a presença da palavra sem looping é construindo umatrie para a sua matriz. Existem muitas implementações trie lá fora (Google"ruby trie"). Vou usar rambling-trie neste exemplo:

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 }
E agora estamos prontos para testar a presença de várias palavras em sua matriz sem looping sobre ela, em tempo, com a mesma simplicidade sintática que Array#include?, usando o sublinear Trie#include?:
trie.include? 'bird' #=> true
trie.include? 'duck' #=> false
 27
Author: Boris Stitnicky, 2013-06-10 16:23:39

Ruby tem 11 métodos para encontrar elementos numa matriz.

O preferido é include?

Ou para acesso repetido, criando um conjunto e depois chamando include? ou member? Aqui estão todos.
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

Todos eles devolvem um valor ish trueSe o elemento estiver presente.

include? é o método preferido. Ele usa um laço C-language for internamente que quebra quando um elemento corresponde às funções rb_equal_opt/rb_equal internas. Não pode ficar muito mais eficiente a menos que crie um conjunto para verificações repetidas de membros.

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ão está redefinido na classe Array e usa uma implementação não otimizada do módulo Enumerable que literalmente enumera através de todos os elementos.

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;
}

Traduzido para o código Ruby isto faz sobre o seguinte

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

Ambos include? e member? têm O(n) complexidade de tempo, uma vez que ambos procuram a matriz para a primeira ocorrência do valor esperado.

Podemos usar um conjunto para obter acesso O(1) tempo ao custo de ter que criar uma representação hash do array primeiro. Se você verificar repetidamente a adesão na mesma matriz este investimento inicial pode pagar rapidamente. Set Não é implementado em C, mas como simples Classe Ruby, ainda o tempo de acesso O(1) do subjacente @hash faz com que isto valha a pena.

Aqui está a implementação da 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

Como pode ver a classe Set apenas cria uma instância interna @hash, mapeia todos os objectos para true e em seguida, verifica a adesão usando Hash#include? que é implementado com O(1) tempo de acesso na classe Hash.

Não vou discutir os outros 7 métodos, porque são todos menos eficientes.

Existem na verdade ainda mais métodos com O(n) complexidade além dos 11 listados acima, mas eu decidi não listá-los desde digitalizar a matriz inteira em vez de quebrar no primeiro jogo.

Não uses isto,

# bad examples
array.grep(element).any? 
array.select { |each| each == element }.size > 0
...
 24
Author: akuhn, 2016-12-25 23:48:10
Se não queres fazer loop, não há maneira de o fazer com Arrays. Devias usar um conjunto.
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

Os conjuntos funcionam internamente como hashs, para que a Ruby não precise de percorrer a colecção para encontrar itens, uma vez que, como o nome indica, gera hashs das chaves e cria um mapa de memória para que cada hash aponte para um certo ponto na memória. O exemplo anterior feito com um Hash:

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

A desvantagem é que os conjuntos e as teclas de hash só podem incluir itens únicos e se você adiciona um monte de itens, Ruby terá que reash a coisa toda depois de um certo número de itens para construir um novo mapa que se adapte a um espaço de chave maior. Para saber mais sobre isso, eu recomendo que você assista [[10]] MountainWest RubyConf 2014-Big O in a Homemade Hash por Nathan Long

Eis uma referência:

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

E os resultados:

      user     system      total        real
array  7.020000   0.000000   7.020000 (  7.031525)
set    0.010000   0.000000   0.010000 (  0.004816)
 16
Author: Kimmo Lehto, 2014-05-29 19:58:44

Esta é outra maneira de fazer isto: usar o método Array#index.

Devolve o índice da primeira ocorrência do elemento na matriz.

Exemplo:

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

O índice () também pode tomar um bloco

Por exemplo

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

Aqui, devolva o índice da primeira palavra na matriz que contém a letra 'o'.

 15
Author: Zack Xu, 2013-10-02 17:22:21
Há várias maneiras de conseguir isto. Alguns deles são os seguintes:
a = [1,2,3,4,5]

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false
 8
Author: sumit, 2016-07-01 15:36:23
Facto Engraçado.

Você pode usar * para verificar a adesão à lista numa expressão case.

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

Observe o pouco * na cláusula de quando, isto verifica para a adesão na matriz.

Todo o comportamento mágico habitual do operador splat aplica-se, por isso, por exemplo, se array não é realmente um array, mas um único elemento que irá corresponder a esse elemento.

 7
Author: akuhn, 2016-12-25 23:48:46

Isto dir-lhe-á não só que existe, mas também quantas vezes aparece:

 a = ['Cat', 'Dog', 'Bird']
 a.count("Dog")
 #=> 1
 5
Author: user3245240, 2014-04-15 18:29:23
Para que conste, os docs de Ruby são um recurso incrível para este tipo de perguntas. Também tomaria Nota do comprimento da matriz que estão a procurar. O método include? irá executar uma pesquisa linear com a complexidade O(n) que pode ficar bastante feia, dependendo do tamanho do array.

Se estiver a trabalhar com uma grande matriz (ordenada), consideraria escrever um algoritmo de pesquisa binária que não deve ser muito difícil e tem um caso pior de O (log n).

Ou se estiver a usar o Ruby 2.0, pode aproveitar-se de bsearch.

 5
Author: davissp14, 2016-12-02 20:47:49

Se tem em mente mais valores... você pode tentar:

Exemplo: Se o gato e o cão existirem na lista:

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

Em vez de:

['Cat','Dog','Bird'].member?('Cat') and ['Cat','Dog','Bird'].include?('Dog')
Nota: membro? e incluir? são iguais.

Isto pode fazer o trabalho em uma linha!

 4
Author: Daniel Antonio Nuñez Carhuayo, 2014-04-07 15:04:57
Também há o contrário!

Suponha que o array é [: edit,: update,: create,: show ] - bem talvez todos os sete pecados mortais/descansados :)

E mais um brinquedo com a ideia de puxar uma acção válida de algum texto-say

O meu irmão quer que eu actualize o perfil dele.

Solução

[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}
 3
Author: walt_die, 2012-10-15 14:08:53

Se quisermos não usar {[1] } isto também funciona:

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

Se você precisar de verificar múltiplos tempos para qualquer chave, Converter arr para hash, e agora verificar em 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

Desempenho de Hash # has_key? versus matriz#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    

Para uma verificação única utilizando include? está bem

 3
Author: aqfaridi, 2018-09-08 16:22:20
Que tal por aqui?
['Cat', 'Dog', 'Bird'].index('Dog')
 2
Author: 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
Author: Rahul Patel, 2016-12-19 08:01:54

Se não queres usar include? você pode primeiro envolver o elemento em um array e, em seguida, verificar se o elemento envolto é igual à interseção do array e do elemento envolto. Isto irá devolver um valor booleano baseado na igualdade.

def in_array?(array, item)
    item = [item] unless item.is_a?(Array)
    item == array & item
end
 0
Author: mgidea, 2014-05-15 06:46:49
Aqui está mais uma maneira de fazer isto:
arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'

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