Verificar se existe um valor numa lista em Ruby
eu tenho um valor 'Dog'
e um array ['Cat', 'Dog', 'Bird']
.
22 answers
include?
:
>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
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.
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?
...
Tenta
['Cat', 'Dog', 'Bird'].include?('Dog')
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).
%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
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
Ruby tem 11 métodos para encontrar elementos numa matriz.
O preferido é include?
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 true
Se 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.
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
.
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
...
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)
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'.
a = [1,2,3,4,5]
2.in? a #=> true
8.in? a #=> false
a.member? 1 #=> true
a.member? 8 #=> false
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.
Isto dir-lhe-á não só que existe, mas também quantas vezes aparece:
a = ['Cat', 'Dog', 'Bird']
a.count("Dog")
#=> 1
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
.
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!
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}[,|.| |]/}
Se quisermos não usar {[1] } isto também funciona:
['cat','dog','horse'].select{ |x| x == 'dog' }.any?
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
['Cat', 'Dog', 'Bird'].index('Dog')
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
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
arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'
present = arr.size != (arr - [e]).size
array = [ 'Cat', 'Dog', 'Bird' ]
array.include?("Dog")