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?
22 answers
Estás buscando include?
:
>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
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?
...
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')
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.
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
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
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
...
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)
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'.
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
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.
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
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
.
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!
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}[,|.| |]/}
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?
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
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')
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
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
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
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")
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