Controlla se esiste un valore in una matrice in Ruby


Answers

C'è un in? metodo in ActiveSupport (parte di Rails) dalla v3.1, come sottolineato da @campaterson. Quindi, all'interno di Rails, o se require 'active_support' , puoi scrivere:

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

OTOH, non c'è operatore in o #in? metodo in Ruby stesso, anche se è stato proposto prima, in particolare da Yusuke Endoh un membro di prima qualità di ruby-core.

Come sottolineato da altri, il metodo inverso include? esiste, per tutti gli Enumerable s inclusi Array , Hash , Set , Range :

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

Si noti che se si hanno molti valori nell'array, verranno controllati uno dopo l'altro (cioè O(n) ), mentre quella ricerca per un hash sarà costante (es. O(1) ). Quindi, se la matrice è costante, ad esempio, è preferibile utilizzare un Set . Per esempio:

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

Un rapido test rivela che la chiamata include? su un 10 elemento Set è circa 3,5 volte più veloce rispetto a chiamarlo sulla Array equivalente (se l'elemento non viene trovato).

Una nota conclusiva: diffidare quando si utilizza include? su un Range , ci sono sottigliezze, quindi fare riferimento al documento e confrontare con la cover? ...

Question

Ho un valore 'Dog' e un array ['Cat', 'Dog', 'Bird'] .

Come posso verificare se esiste nell'array senza doverlo scorrere? C'è un modo semplice per verificare se il valore esiste, niente di più?




['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true



Se hai in mente più valori ... puoi provare:

Esempio: se Gatto e Cane esistono nell'array:

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

Invece di:

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

Nota: membro? e includere? sono uguali

Questo può fare il lavoro in una sola riga!




Questo ti dirà non solo che esiste ma anche quante volte appare:

 a = ['Cat', 'Dog', 'Bird']
 a.count("Dog")
 #=> 1



Diverse risposte suggeriscono l' Array#include? , ma c'è un avvertimento importante: guardando la fonte, anche l' Array#include? esegue il 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;
}

Il modo per testare la presenza della parola senza il loop è costruendo un trie per il tuo array. Ci sono molte implementazioni trie là fuori (google "ruby trie"). rambling-trie in questo esempio:

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 ora siamo pronti a testare la presenza di varie parole nell'array senza ricorrere al loop, in O(log n) time, con la stessa semplicità sintattica di Array#include? , usando l' Trie#include? sublineare Trie#include? :

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



se non vuoi usare include? è possibile prima avvolgere l'elemento in una matrice e quindi verificare se l'elemento avvolto è uguale all'intersezione dell'array e dell'elemento avvolto. Ciò restituirà un valore booleano basato sull'eguaglianza.

def in_array?(array, item)
    item = [item] unless item.is_a?(Array)
    item == array & item
end



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



Ci sono molti modi per farlo. Alcuni di loro sono i seguenti:

a = [1,2,3,4,5]

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false



Se non vogliamo usare include? questo funziona anche:

['cat','dog','horse'].select{ |x| x == 'dog' }.any?



Se non vuoi eseguire il ciclo, non c'è modo di farlo con gli array. Dovresti usare invece un Set.

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

Imposta il lavoro internamente come hash, quindi Ruby non ha bisogno di scorrere la collezione per trovare gli oggetti, poiché come suggerisce il nome, genera degli hash delle chiavi e crea una mappa di memoria in modo che ogni hash punti a un certo punto nella memoria. L'esempio precedente fatto con un hash:

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

Lo svantaggio è che i set e le chiavi hash possono includere solo oggetti unici e se aggiungete molti oggetti, Ruby dovrà ripetere l'intera operazione dopo un certo numero di elementi per costruire una nuova mappa che si adatta a uno spazio chiavi più grande. Per ulteriori informazioni su questo, ti consiglio di guardare MountainWest RubyConf 2014 - Big O in un hash fatto in casa di Nathan Long

Ecco un punto di riferimento:

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 i risultati:

      user     system      total        real
array  7.020000   0.000000   7.020000 (  7.031525)
set    0.010000   0.000000   0.010000 (  0.004816)



Utilizza Enumerable#include :

a = %w/Cat Dog Bird/

a.include? 'Dog'

Oppure, se viene eseguito un numero di test, 1 puoi eliminare il loop (che include? anche has) e passare da O (n) a O (1) con:

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

1. Spero che questo sia ovvio, ma a parte le obiezioni: sì, solo per alcune occhiate, le operazioni di Hash [] e trasposizione dominano il profilo e sono ciascuna di esse (n) .




Related



Tags

ruby ruby   arrays