Comprobar si existe un valor en una matriz en Ruby


Tengo un valor 'Dog' y una matriz ['Cat', 'Dog', 'Bird'].

¿Cómo compruebo si existe en el array sin pasar por él? ¿Hay una forma sencilla de comprobar si el valor existe, nada más?

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

22 answers

Estás buscando include?:

>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
 1687
Author: Brian Campbell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-04-29 12:02:04

Hay un in? método en ActiveSupport (parte de Rails) desde v3.1, como señaló @campaterson. Así que dentro de Rails, o si require 'active_support', puedes escribir:

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

OTOH, no hay in operador o #in?método en Ruby mismo, a pesar de que se ha propuesto antes, en particular por Yusuke Endoh un miembro de primera clase de ruby-core.

Como han señalado otros, el método inversoinclude? exists, for all Enumerable s including Array, Hash, Set, Range:

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

Tenga en cuenta que si tiene muchos valores en su matriz, todos se verificarán uno tras otro (es decir, O(n)), mientras que la búsqueda de un hash será un tiempo constante (es decir, O(1)). Así que si el array es constante, por ejemplo, es una buena idea usar un Set en su lugar. Por ejemplo:

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

Una prueba rápida revela que llamar a include? en un elemento 10 Set es aproximadamente 3,5 veces más rápido que llamarlo en el equivalente Array (si el elemento no es encontrar).

Una nota final de cierre: tenga cuidado al usar include? en un Range, hay sutilezas, por lo que consulte el doc y compare con cover?...

 209
Author: Marc-André Lafortune,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-10 20:27:27

Intenta

['Cat', 'Dog', 'Bird'].include?('Dog')
 157
Author: schmitzelburger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-12-31 17:52:28

Use Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

O, si se realizan varias pruebas,1 puedes deshacerte del bucle (que incluso include? tiene) y pasar de O (n) a O (1) con:

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


1. Espero que esto sea obvio, pero para evitar objeciones: sí, para solo unas pocas búsquedas, el Hash [] y transpose ops dominan el perfil y son cada O(n) ellos mismos.
 44
Author: DigitalRoss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-06 00:18:29

Si desea comprobar por un bloque, usted podría probar cualquiera? o todos?.

%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  

Los Detalles están aquí: http://ruby-doc.org/core-1.9.3/Enumerable.html
Mi inspiración viene de aquí: https://stackoverflow.com/a/10342734/576497

 42
Author: Van,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:48

Varias respuestas sugieren Array#include?, pero hay una advertencia importante: Mirando la fuente, incluso Array#include? realiza un bucle:

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 forma de probar la presencia de la palabra sin bucle es construyendo un trie para su matriz. Hay muchas implementaciones de trie por ahí (google "ruby trie"). Usaré rambling-trie en este ejemplo:

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 }

Y ahora estamos listos para probar la presencia de varias palabras en su matriz sin bucle sobre ella, en O(log n) tiempo, con lo mismo simplicidad sintáctica como Array#include?, usando Trie#include? sublineal:

trie.include? 'bird' #=> true
trie.include? 'duck' #=> false
 27
Author: Boris Stitnicky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-10 16:23:39

Ruby tiene 11 métodos para encontrar elementos en un array.

El preferido es include?

O para el acceso repetido, creando un conjunto y luego llamando a include? o member?

Aquí están todos ellos,

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 ellos devuelven un valor ish true si el elemento está presente.

include? es el método preferido. Utiliza internamente un bucle C-language for que se rompe cuando un elemento coincide con las funciones internas rb_equal_opt/rb_equal. No puede ser mucho más eficiente a menos que se crea un conjunto para verificaciones de membresía repetidas.

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? no se redefine en la clase Array y utiliza una implementación no optimizada del módulo Enumerable que literalmente enumera a través de todos los 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;
}

Traducido al código Ruby esto hace sobre lo siguiente

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

Tanto include? como member? tienen O(n) complejidad temporal desde que ambos buscan en el array la primera ocurrencia del valor esperado.

Podemos usar un conjunto para obtener O(1) tiempo de acceso a costa de tener que crear primero una representación hash del array. Si comprueba repetidamente la membresía en la misma matriz, esta inversión inicial puede dar sus frutos rápidamente. Set no está implementado en C sino como clase Ruby simple, aún así el tiempo de acceso O(1) del @hash subyacente hace que esto valga la pena.

Aquí está la implementación de la clase 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 puede ver, la clase Set simplemente crea una instancia interna @hash, asigna todos los objetos a true y luego comprueba la pertenencia usando Hash#include? que se implementa con O(1) tiempo de acceso en la clase Hash.

No voy a discutir los otros 7 métodos, ya que todos son menos eficientes.

En realidad hay aún más métodos con O(n) complejidad más allá de los 11 enumerados anteriormente, pero decidí no enumerarlos ya que escanear todo el array en lugar de romper en la primera coincidencia.

No uses estos,{[32]]}

# bad examples
array.grep(element).any? 
array.select { |each| each == element }.size > 0
...
 24
Author: akuhn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-25 23:48:10

Si no desea realizar un bucle, no hay forma de hacerlo con matrices. Deberías usar un set en su lugar.

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

Los conjuntos funcionan internamente como hashes, por lo que Ruby no necesita recorrer la colección para encontrar elementos, ya que como su nombre lo indica, genera hashes de las claves y crea un mapa de memoria para que cada hash apunte a un punto determinado de la memoria. El ejemplo anterior hecho con un Hash:

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

La desventaja es que los conjuntos y las claves hash solo pueden incluir elementos únicos y si añade muchos elementos, Ruby tendrá que rehacer todo después de cierto número de elementos para construir un nuevo mapa que se adapte a un espacio de claves más grande. Para más información sobre esto, te recomiendo ver MountainWest RubyConf 2014 - Big O in a Homemade Hash by Nathan Long

Aquí hay un punto de referencia:

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

Y los 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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-29 19:58:44

Esta es otra forma de hacer esto: use el método Array#index.

Devuelve el índice de la primera aparición del elemento en el array.

Ejemplo:

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

Index () también puede tomar un bloque

Por ejemplo

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

Aquí, devuelve el índice de la primera palabra en el array que contiene la letra 'o'.

 15
Author: Zack Xu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-10-02 17:22:21

Hay múltiples maneras de lograr esto. Algunos de ellos son los siguientes:

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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-01 15:36:23

Dato curioso,

Puede usar * para verificar la pertenencia a una matriz en expresiones case.

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

Observe el pequeño * en la cláusula when, esto comprueba la pertenencia a la matriz.

Se aplica todo el comportamiento mágico habitual del operador splat, por ejemplo, si array no es realmente un array sino un solo elemento, coincidirá con ese elemento.

 7
Author: akuhn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-25 23:48:46

Esto te dirá no solo que existe sino también cuántas veces aparece:

 a = ['Cat', 'Dog', 'Bird']
 a.count("Dog")
 #=> 1
 5
Author: user3245240,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-15 18:29:23

Para lo que vale, El Ruby docs son un recurso increíble para este tipo de preguntas.

También me gustaría tomar nota de la longitud de la matriz que está buscando a través. El método include? ejecutará una búsqueda lineal con complejidad O(n) que puede ser bastante fea dependiendo del tamaño de la matriz.

Si está trabajando con una matriz grande (ordenada), consideraría escribir un algoritmo de búsqueda binaria que no debería ser demasiado difícil y tiene un peor caso de O (log n).

O si estás usando Ruby 2.0, puedes aprovechar bsearch.

 5
Author: davissp14,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-02 20:47:49

Si tienes en mente más valores... puedes probar:

Ejemplo: si el Gato y el perro existen en la matriz:

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

En lugar de:

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

Nota: ¿miembro? ¿e incluir? son iguales.

Esto puede hacer el trabajo en una línea!

 4
Author: Daniel Antonio Nuñez Carhuayo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-07 15:04:57

¡También está al revés!

Supongamos que la matriz es [: edit, :update, :create,: show ] - bueno, tal vez todos los siete pecados mortales/reparadores :)

Y además jugar con la idea de tirando una acción válida de alguna cadena-decir

Mi hermano quiere que actualice su perfil

Solución

[ :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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-15 14:08:53

Si no queremos usar include? esto también funciona:

['cat','dog','horse'].select{ |x| x == 'dog' }.any?
 3
Author: xlembouras,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-20 07:24:06

Si necesita verificar múltiplos veces para cualquier clave, convierta arr a hash, y ahora verifique 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

Rendimiento de Hash#has_key? versus Array # 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 la comprobación de tiempo único usando include? está bien

 3
Author: aqfaridi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-08 16:22:20

¿Qué tal de esta manera?

['Cat', 'Dog', 'Bird'].index('Dog')
 2
Author: ajahongir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
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,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-19 08:01:54

Si no desea utilizar include? primero puede envolver el elemento en una matriz y luego comprobar si el elemento envuelto es igual a la intersección de la matriz y el elemento envuelto. Esto devolverá un valor booleano basado en la igualdad.

def in_array?(array, item)
    item = [item] unless item.is_a?(Array)
    item == array & item
end
 0
Author: mgidea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-15 06:46:49

Aquí hay una forma más de hacer esto:

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

present = arr.size != (arr - [e]).size
 0
Author: Wand Maker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-05 07:04:34
array = [ 'Cat', 'Dog', 'Bird' ]
array.include?("Dog")
 0
Author: Matthew Maurice,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-29 04:23:41