Come verificare se esiste un valore in un array in Ruby


1315

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

Come posso verificare se esiste nell'array senza eseguirne il loop? Esiste un modo semplice per verificare l'esistenza del valore, niente di più?


5
usare il .include? metodo . Restituisce un valore booleano che è quello che vuoi. Nel tuo caso basta digitare: ['Cat', 'Dog', 'Bird']. Include ('Dog') e dovrebbe restituire il valore booleano true.
Jwan622,

non usare include? metodo se si desidera controllare più volte la presenza di valori diversi nell'array o perché includere? ogni volta ripeterà l'array prendendo l'operazione O (n) per cercare ogni volta, invece fai un hash hash = arr.map {|x| [x,true]}.to_h, ora controlla se hash.has_key? 'Dog' restituisce vero o no
aqfaridi,

3
Non puoi davvero farlo totalmente "senza farcela". È logicamente impossibile, il computer non può sapere con certezza se l'array contiene l'elemento senza scorrere gli elementi per verificare se uno di essi è quello che sta cercando. A meno che non sia vuoto, ovviamente. Quindi suppongo che non ti serva un loop.
Tim M.

Vedere i benchmark di seguito per i test della differenza tra i vari modi per trovare un elemento in un array e un set. stackoverflow.com/a/60404934/128421
Tin Man,

Risposte:


1946

Stai cercando include?:

>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true

73
Sintassi alternativa:%w(Cat Dog Bird).include? 'Dog'
scarver2

184
A volte vorrei che fosse "contiene" non includere. Lo confondo sempre con include.
Henley Chiu,

19
Vorrei solo notare che internamente, #include?esegue ancora il looping. Tuttavia, il programmatore viene salvato dalla scrittura esplicita del ciclo. Ho aggiunto una risposta che esegue l'attività davvero senza loop.
Boris Stitnicky il

8
@HenleyChiu I che è stato chiamato[ 'Dog', 'Bird', 'Cat' ].has? 'Dog'

4
@AlfonsoVergara Sì, qualsiasi soluzione per un array deve eseguire internamente una sorta di looping; non è possibile verificare l'appartenenza a un array senza loop. Se non si desidera eseguire alcun ciclo anche internamente, è necessario utilizzare una struttura dati diversa, ad esempio una tabella hash perfetta con chiavi di dimensioni fisse. Dato che non c'è modo di testare l'appartenenza a un array senza eseguire il loop interno, ho interpretato la domanda nel senso "senza dover scrivere il loop in modo esplicito me stesso"
Brian Campbell,

250

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

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

OTOH, non c'è nessun inoperatore o #in?metodo in Ruby stesso, anche se è stato proposto in precedenza, in particolare da Yusuke Endoh un membro di prim'ordine di ruby-core.

Come sottolineato da altri, il metodo inverso include?esiste, per tutti Enumerables compresi Array, Hash, Set, Range:

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

Nota che se hai molti valori nel tuo array, saranno tutti controllati uno dopo l'altro (es. O(n)), Mentre quella ricerca di un hash sarà un tempo costante (es O(1).). Quindi, se l'array è costante, ad esempio, è una buona idea usare 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 test rapido rivela che chiamare include?un elemento 10 Setè circa 3,5 volte più veloce di chiamarlo sull'equivalente Array(se l'elemento non viene trovato).

Un'ultima nota di chiusura: fai attenzione quando usi include?su a Range, ci sono delle sottigliezze, quindi fai riferimento al documento e confronta con cover?...


2
Sebbene Ruby non includa #in?nel suo core, se stai usando Rails, è disponibile. api.rubyonrails.org/classes/Object.html#method-i-in-3F (So ​​che questa è una domanda di Ruby, non di Rails, ma può aiutare chiunque cerchi di utilizzare #in?in Rails. Sembra che sia stato aggiunto in Rails 3.1 apidock.com/rails/Object/in%3F
campeterson

166

Provare

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

questa è la sintassi più vecchia, guarda ^^^ @ brian's answer
jahrichie

62
@jahrichie cosa consideri esattamente "vecchia sintassi" in questa risposta, le parentesi opzionali?
Dennis,

3
sono d'accordo @Dennis, questo non è più vecchio, le parentesi sono opzionali e nella maggior parte dei casi una BUONA PRATICA .... prova ad usare includi senza parentesi in una riga se frase per esempio, ciò che intendevo è che a seconda del tuo caso tu dovrebbe o deve usare le parentesi oppure no (non correlato affatto con le "vecchie" sintassi ruby)
d1jhoni1b

Questa è l'unica sintassi che potrei riuscire a lavorare all'interno di un'operazione ternaria.
Michael Cameron,

49

Utilizzare Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

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

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


1. Spero che sia ovvio, ma per evitare obiezioni: sì, solo per alcune ricerche, le operazioni Hash [] e trasposizione dominano il profilo e sono ciascuna O (n) stesse.


49

Se si desidera verificare con un blocco, è possibile provare any?o all?.

%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  

Vedi Enumerable per ulteriori informazioni.

La mia ispirazione è venuta da " valuta se l'array ha degli elementi in rubino "


1
Molto utile se si desidera verificare che una o tutte queste stringhe siano incluse in un'altra stringa / costante
thanikkal

43

Ruby ha undici metodi per trovare elementi in un array.

Il preferito è include?o, per un accesso ripetuto, creare un set e quindi chiamare include?o member?.

Eccone tutti:

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

Restituiscono tutti un truevalore ish se l'elemento è presente.

include?è il metodo preferito. Utilizza forinternamente un ciclo in linguaggio C che si interrompe quando un elemento corrisponde alle rb_equal_opt/rb_equalfunzioni interne . Non può essere molto più efficiente se non si crea un set per controlli di iscrizione ripetuti.

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?non viene ridefinito nella Arrayclasse e utilizza un'implementazione non ottimizzata dal Enumerablemodulo che elenca letteralmente tutti gli elementi:

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

Tradotto in codice Ruby, ciò comporta quanto segue:

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

Entrambi include?e member?hanno una complessità temporale O (n) poiché entrambi cercano nell'array la prima occorrenza del valore atteso.

Possiamo usare un Set per ottenere il tempo di accesso O (1) al costo di dover prima creare una rappresentazione Hash dell'array. Se controlli ripetutamente l'appartenenza sullo stesso array, questo investimento iniziale può ripagare rapidamente. Setnon è implementato in C ma come semplice classe Ruby, comunque il tempo di accesso O (1) del sottostante @hashrende questo utile.

Ecco l'implementazione della 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

Come puoi vedere, la classe Set crea solo @hashun'istanza interna , mappa tutti gli oggetti truee quindi controlla l'appartenenza utilizzando il Hash#include?quale è implementato con il tempo di accesso O (1) nella classe Hash.

Non discuterò degli altri sette metodi poiché sono tutti meno efficienti.

Esistono in realtà ancora più metodi con complessità O (n) oltre gli 11 sopra elencati, ma ho deciso di non elencarli poiché eseguono la scansione dell'intero array anziché interrompere alla prima corrispondenza.

Non usare questi:

# bad examples
array.grep(element).any? 
array.select { |each| each == element }.size > 0
...

Che sfacciataggine nel dire che Ruby ha esattamente 11 modi per fare qualsiasi cosa! Non appena dici che qualcuno ti farà notare che ti sei perso # 12, quindi # 13 e così via. Per chiarire il mio punto, suggerirò altri modi, ma prima lasciami mettere in discussione i 11modi in cui hai elencato. Innanzitutto, difficilmente puoi contareindex e find_index(o finde detect) come metodi separati, poiché sono solo nomi diversi per lo stesso metodo. In secondo luogo, tutte le espressioni che finiscono con > 0sono errate, che sono sicuro fosse una svista. (cont.)
Cary Swoveland,

... arr.index(e), ad esempio, restituisce 0if arr[0] == e. Ricorderai i arr.index(e)ritorni nilse enon sono presenti. indexnon può essere utilizzato, tuttavia, se si sta cercando nilin arr. (Stesso problema con rindex, che non è elencato.). La conversione dell'array in un set e quindi l'utilizzo di metodi set è un po 'allungato. Perché non convertire poi in un hash (con chiavi dall'array e valori arbitrari), quindi utilizzare i metodi hash? Anche se la conversione in un set è OK, ci sono altri metodi di set che potrebbero essere usati, come !arr.to_set.add?(e). (cont.)
Cary Swoveland,

... Come promesso, ecco ci sono alcuni più metodi che potrebbero essere utilizzati: arr.count(e) > 0, arr != arr.dup.delete(e) , arr != arr - [e]e arr & [e] == [e]. Si potrebbe anche impiegare selectereject .
Cary Swoveland,

Consiglio vivamente di utilizzare un set se il criterio è che non sono consentiti duplicati e di sapere se un particolare elemento esiste nell'elenco. Il set è davvero molto più veloce. Le matrici in realtà non dovrebbero essere usate quando è necessaria la ricerca; Sono meglio utilizzati come una coda in cui le cose vengono temporaneamente archiviate per essere elaborate in ordine. Hash e Set sono migliori per cercare di vedere se esiste qualcosa.
Tin Man

30

Diverse risposte suggeriscono Array#include?, ma c'è un avvertimento importante: guardando la fonte, Array#include?si esegue anche il loop:

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 loop è costruendo un trie per l'array. Ci sono molte implementazioni di trie là fuori (google "ruby trie"). Userò rambling-triein 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 nel tuo array senza scorrere su di esso, nel O(log n)tempo, con la stessa semplicità sintattica di Array#include?, usando sublineare Trie#include?:

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

8
a.each do ... endUmm ... non so come non sia un loop
tckmn il

27
Si noti che questo include effettivamente un ciclo; tutto ciò che non è O (1) include una sorta di loop. Capita solo di essere un ciclo sopra i caratteri della stringa di input. Nota anche una risposta già menzionata Set#include?per le persone che sono preoccupate per l'efficienza; accoppiato con l'utilizzo di simboli anziché di stringhe, può essere O (1) caso medio (se si utilizzano stringhe, il calcolo dell'hash è O (n) dove n è la lunghezza della stringa). O se si desidera utilizzare librerie di terze parti, è possibile utilizzare un hash perfetto che è il caso peggiore O (1).
Brian Campbell,

4
AFAIK, Setusa gli hash per indicizzare i suoi membri, quindi in realtà Set#include? dovrebbe essere di complessità O (1) per un ben distribuito Set(più specificamente O (input-size) per l'hash, e O (log (n / bucket-number)) per la ricerca)
Uri Agassi

11
Il costo di creazione e manutenzione del trie è altrettanto. Se si stanno eseguendo molte operazioni di ricerca sull'array, vale la pena memoria e tempo per popolare un trie e mantenerlo, ma per singoli, o addirittura centinaia o migliaia di controlli, O (n) è perfettamente adatto. Un'altra opzione che non richiede l'aggiunta di dipendenze sarebbe quella di ordinare l'array o mantenerlo in ordine, nel qual caso un'operazione di ricerca binaria O (lg n) può essere utilizzata per verificare l'inclusione.
speakingcode

1
@speakingcode, potresti avere ragione dal punto di vista pragmatico. Ma l'OP chiede di "verificare se il valore esiste, niente di più, senza loop". Quando ho scritto questa risposta, c'erano molte soluzioni pragmatiche qui, ma nessuna che potesse effettivamente soddisfare il requisito letterale del richiedente. La tua osservazione che i BST sono correlati ai tentativi è corretta, ma per le stringhe, trie è lo strumento giusto per il lavoro, anche Wikipedia lo sa molto . La complessità di costruire e mantenere un trie ben implementato è sorprendentemente favorevole.
Boris Stitnicky,

18

Se non vuoi eseguire il looping, non c'è modo di farlo con gli array. Invece dovresti usare 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

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

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

Il rovescio della medaglia è che i tasti Set e Hash possono includere solo oggetti unici e se aggiungi molti oggetti, Ruby dovrà ripassare il tutto dopo un certo numero di elementi per costruire una nuova mappa che si adatti a uno spazio chiave più grande. Per ulteriori informazioni, ti consiglio di guardare " MountainWest RubyConf 2014 - Big O in a 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)

Se si utilizza il rilevamento, è possibile almeno ridurre il looping. detect si fermerà al primo elemento 'rilevato' (il blocco passato per l'oggetto viene considerato vero). Inoltre, puoi dire rilevare cosa fare se non viene rilevato nulla (puoi passare in una lambda).
Aver il

1
@aenw non si include?ferma al primo colpo?
Kimmo Lehto,

hai assolutamente ragione. Sono così abituato a usare il rilevamento che mi ero dimenticato di includere. grazie per il tuo commento - mi ha assicurato di aver aggiornato le mie conoscenze.
aenw

include?si interrompe al primo hit ma se quel hit si trova alla fine dell'elenco .... Qualsiasi soluzione che si basa su un array per l'archiviazione avrà prestazioni degradanti man mano che l'elenco cresce, soprattutto quando si deve trovare un elemento alla fine dell'elenco elenco. Hash e Set non hanno questo problema, né un elenco ordinato e una ricerca binaria.
Tin Man,

Questo è praticamente il motivo di questa risposta in primo luogo :)
Kimmo Lehto

17

Questo è un altro modo per farlo: usa il Array#indexmetodo.

Restituisce l'indice della prima occorrenza dell'elemento nella matrice.

Per esempio:

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

index() può anche prendere un blocco:

Per esempio:

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

Ciò restituisce l'indice della prima parola nella matrice che contiene la lettera 'o'.


indexcontinua a scorrere sull'array, restituisce solo il valore dell'elemento.
Tin Man,

13

Fatto divertente,

È possibile utilizzare *per verificare l'appartenenza all'array in caseespressioni.

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

Notare il piccolo *nella clausola when, questo controlla l'appartenenza alla matrice.

Si applica tutto il solito comportamento magico dell'operatore splat, quindi ad esempio se in arrayrealtà non è un array ma un singolo elemento corrisponderà a quell'elemento.


Buono a sapersi..!
Cary Swoveland,

Sarebbe anche un primo controllo lento in una dichiarazione del caso, quindi lo userei nell'ultimo whenpossibile, quindi altri controlli più veloci, verrebbero eliminati rapidamente.
Tin Man,

9

Esistono diversi modi per ottenere questo risultato. Alcuni di questi 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

6
Si noti che è Object#in?stato aggiunto solo a Rails (cioè ActiveSupport) v3.1 +. Non è disponibile nel core Ruby.
Tom Lord,

5

Se devi controllare i tempi multipli per qualsiasi chiave, converti arrin hash, e ora controlla in 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

Performance di Hash # has_key? versus Array # include?

Parametro Hash # has_key? Array # includono

Complessità temporale O (1) operazione O (n) operazione 

Tipo di accesso Accede a Hash [chiave] se scorre attraverso ciascun elemento
                        restituisce qualsiasi valore quindi dell'array fino ad esso
                        true viene restituito a trova il valore in Array
                        Hash # has_key? chiamata
                        chiamata    

Per il controllo del tempo singolo, usare include?va bene


Non stai ancora eseguendo il loop nell'array per convertirlo in un hash?
chrisjacob,

2
sì, l'ho fatto, ma ora ho una risposta pre-calcolata per verificare se esiste qualche elemento nell'array o meno. Ora, la prossima volta che cercherò un'altra parola, ci vorrà O (1) per la query, giusto anche se la pre-computazione prenderà O (n). In realtà ho usato include? nella mia applicazione all'interno del ciclo per verificare se un particolare elemento esiste nell'array o no, rovina le prestazioni, ha verificato in ruby-prof, che era il collo di bottiglia
aqfaridi,

1
.... ma O (n) spazio e, inoltre, no è O (n) tempo, perché come giustamente sottolinea @ chris72205, devi prima scorrere nell'array. O (1) + O (n) = O (n). Quindi, in effetti, questo è molto peggio di includere?
Rambatino,

Amico, stavo solo dicendo che non usare include dentro il ciclo, per il controllo a tempo singolo usando include va bene. Quindi leggi prima il caso d'uso, è meglio se devi controllare più volte all'interno del loop.
aqfaridi,

La conversione di un array in un hash è costoso, ma l'utilizzo di un array per la ricerca è una struttura errata. Le matrici sono migliori come code in cui l'ordine potrebbe essere importante; Gli hash e i set sono migliori quando l'inclusione / esistenza / unicità sono importanti. Inizia con il contenitore giusto e il problema è controverso.
Tin Man,

4

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

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

12
Non ha senso usarlo a meno che tu non voglia sapere quante volte appare, tuttavia, poiché .any?tornerà non appena trova il primo elemento corrispondente, .countelaborerà sempre l'intero array.
Zaz,

Anche se tecnicamente questo dirà se qualcosa esiste, NON è anche il modo giusto di farlo se ti interessa la velocità.
Tin Man,

4

Per quello che vale, i documenti di Ruby sono una risorsa straordinaria per questo tipo di domande.

Vorrei anche prendere nota della lunghezza dell'array che stai cercando. Il include?metodo eseguirà una ricerca lineare con complessità O (n) che può diventare piuttosto brutta a seconda delle dimensioni dell'array.

Se stai lavorando con un array di grandi dimensioni (ordinato), prenderei in considerazione la scrittura di un algoritmo di ricerca binaria che non dovrebbe essere troppo difficile e che presenta il caso peggiore di O (log n).

O se stai usando Ruby 2.0, puoi trarne vantaggio bsearch.


3
Una ricerca binaria presuppone che l'array sia ordinato (o ordinato in una forma) che può essere costoso per array di grandi dimensioni, spesso negando il vantaggio.
Tin Man,

Una ricerca binaria richiede anche che tutte le coppie di elementi siano comparabili <=>, il che non è sempre il caso. Supponiamo, ad esempio, che gli elementi dell'array siano hash.
Cary Swoveland,

1
@CarySwoveland dovrebbe essere più o meno sottinteso che gli elementi all'interno di un array ordinato sono comparabili.
davissp14,

4

Puoi provare:

Esempio: se Cat e Dog 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: member?e include?sono uguali.

Questo può fare il lavoro in una riga!


3

Se non vogliamo usarlo, include?funziona anche:

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

3
qualunque? accetta anche blocchi: ['cat', 'dog', 'horse']. any? {| x | x == 'dog'}
maikonas,

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

['Cat', nil, 'Dog'].detect { |x| x == nil } #=> nil. È stato niltrovato?
Cary Swoveland,

2

Che ne dici di questo?

['Cat', 'Dog', 'Bird'].index('Dog')

Continuerà a scorrere sull'array solo per trovare l'elemento. Quindi deve restituire l'indice di quell'elemento.
Tin Man,

2

Se stai provando a farlo in un test di unità MiniTest , puoi usarlo assert_includes. Esempio:

pets = ['Cat', 'Dog', 'Bird']
assert_includes(pets, 'Dog')      # -> passes
assert_includes(pets, 'Zebra')    # -> fails 

2

C'è il contrario.

Supponiamo che l'array sia [ :edit, :update, :create, :show ], beh, forse tutti e sette i peccati mortali / riposanti .

E poi gioca con l'idea di tirare un'azione valida da qualche stringa:

"my brother would like me to update his profile"

Poi:

[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}

1
Il tuo regex, /[,|.| |]#{v.to_s}[,|.| |]/mi fa pensare che volessi trovare "il nome dell'azione circondato da uno di: virgola, punto, spazio o niente", ma ci sono alcuni bug sottili. "|update|"sarebbe tornato [:update]e "update"sarebbe tornato []. Le classi di caratteri ( [...]) non usano le pipe ( |) per separare i caratteri. Anche se li cambiamo in gruppi ( (...)), non puoi abbinare un personaggio vuoto. Quindi la regex che probabilmente volevi è/(,|\.| |^)#{v.to_s}(,|\.| |$)/
bkDJ,

il regex è ok (controllato su rubular.com) - e @Rambatino: il perché sarebbe come Amazon Echo per esempio;) Potresti dire: "per favore aggiungi il pollo alla mia lista della spesa" (e - beh - allora dovresti aggiungi il: aggiungi all'array, ma credo che tu ne abbia il senso;)
walt_die

1
"regex è ok (controllato rubular.com)". Non va bene Il tuo regex non corrisponderà a una parola chiave all'inizio o alla fine di una stringa (ad es. "Aggiorna il profilo di mio fratello"). Se non vuoi abbinare l'inizio o la fine, allora la tua regex non è ancora corretta perché la classe di caratteri su entrambi i lati della parola chiave dovrebbe essere/[,. ]/
bkDJ

Come dice @bkDJ, la regex è sbagliata. rubular.com/r/4EG04rANz6KET6
the Tin Man

1

Se si desidera restituire il valore non solo vero o falso, utilizzare

array.find{|x| x == 'Dog'}

Questo restituirà 'Cane' se esiste nell'elenco, altrimenti zero.


O l'uso array.any?{|x| x == 'Dog'}se non volete vero / falso (non il valore), ma anche desidera confrontare contro un blocco come questo.
mahemoff,

0

Ecco un altro modo per farlo:

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

present = arr.size != (arr - [e]).size

1
Questo è un modo orribilmente inefficiente per farlo! Non sto effettuando il downvoting perché non è tecnicamente scorretto e qualcuno potrebbe imparare qualcosa su Ruby leggendolo, ma ci sono molte risposte migliori sopra.
Gibuti,

Conehead, puoi semplificare arr != arr - [e]. arr & [e] == [e]è un altro modo sulla stessa linea.
Cary Swoveland,

@CarySwoveland Non prendere in giro il cappello di un mago ;-)
Wand Maker

Mi riferivo alla testa del mago, non al cappello, con il massimo rispetto.
Cary Swoveland,


0

ha molti modi per trovare un elemento in qualsiasi array ma il modo più semplice è 'in?' metodo.

example:
arr = [1,2,3,4]
number = 1
puts "yes #{number} is present in arr" if number.in? arr

2
Nota: Come descritto in questa risposta , il metodo in?richiede ActiveSupportda importare: require active_support.
Patrick,

Non richiede tutto ActiveSupport se si utilizzano le estensioni core .
Tin Man,

0

se non si desidera utilizzare, include?è possibile innanzitutto avvolgere l'elemento in una matrice e quindi verificare se l'elemento a capo è uguale all'intersezione della matrice e all'elemento a capo. Ciò restituirà un valore booleano basato sull'uguaglianza.

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

-1

Trovo sempre interessante eseguire alcuni benchmark per vedere la velocità relativa dei vari modi di fare qualcosa.

La ricerca di un elemento array all'inizio, al centro o alla fine influirà su qualsiasi ricerca lineare ma influenzerà a malapena una ricerca rispetto a un set.

La conversione di un array in un set provocherà un colpo nel tempo di elaborazione, quindi crea il set da un array una volta o inizia con un set dall'inizio.

Ecco il codice di riferimento:

# frozen_string_literal: true

require 'fruity'
require 'set'

ARRAY = (1..20_000).to_a
SET = ARRAY.to_set

DIVIDER = '-' * 20

def array_include?(elem)
  ARRAY.include?(elem)
end

def array_member?(elem)
  ARRAY.member?(elem)
end

def array_index(elem)
  ARRAY.index(elem) >= 0
end

def array_find_index(elem)
  ARRAY.find_index(elem) >= 0
end

def array_index_each(elem)
  ARRAY.index { |each| each == elem } >= 0
end

def array_find_index_each(elem)
  ARRAY.find_index { |each| each == elem } >= 0
end

def array_any_each(elem)
  ARRAY.any? { |each| each == elem }
end

def array_find_each(elem)
  ARRAY.find { |each| each == elem } != nil
end

def array_detect_each(elem)
  ARRAY.detect { |each| each == elem } != nil
end

def set_include?(elem)
  SET.include?(elem)
end

def set_member?(elem)
  SET.member?(elem)
end

puts format('Ruby v.%s', RUBY_VERSION)

{
  'First' => ARRAY.first,
  'Middle' => (ARRAY.size / 2).to_i,
  'Last' => ARRAY.last
}.each do |k, element|
  puts DIVIDER, k, DIVIDER

  compare do
    _array_include?        { array_include?(element)        }
    _array_member?         { array_member?(element)         }
    _array_index           { array_index(element)           }
    _array_find_index      { array_find_index(element)      }
    _array_index_each      { array_index_each(element)      }
    _array_find_index_each { array_find_index_each(element) }
    _array_any_each        { array_any_each(element)        }
    _array_find_each       { array_find_each(element)       }
    _array_detect_each     { array_detect_each(element)     }
  end
end

puts '', DIVIDER, 'Sets vs. Array.include?', DIVIDER
{
  'First' => ARRAY.first,
  'Middle' => (ARRAY.size / 2).to_i,
  'Last' => ARRAY.last
}.each do |k, element|
  puts DIVIDER, k, DIVIDER

  compare do
    _array_include? { array_include?(element) }
    _set_include?   { set_include?(element)   }
    _set_member?    { set_member?(element)    }
  end
end

Che, quando eseguito sul mio laptop Mac OS, si traduce in:

Ruby v.2.7.0
--------------------
First
--------------------
Running each test 65536 times. Test will take about 5 seconds.
_array_include? is similar to _array_index
_array_index is similar to _array_find_index
_array_find_index is faster than _array_any_each by 2x ± 1.0
_array_any_each is similar to _array_index_each
_array_index_each is similar to _array_find_index_each
_array_find_index_each is faster than _array_member? by 4x ± 1.0
_array_member? is faster than _array_detect_each by 2x ± 1.0
_array_detect_each is similar to _array_find_each
--------------------
Middle
--------------------
Running each test 32 times. Test will take about 2 seconds.
_array_include? is similar to _array_find_index
_array_find_index is similar to _array_index
_array_index is faster than _array_member? by 2x ± 0.1
_array_member? is faster than _array_index_each by 2x ± 0.1
_array_index_each is similar to _array_find_index_each
_array_find_index_each is similar to _array_any_each
_array_any_each is faster than _array_detect_each by 30.000000000000004% ± 10.0%
_array_detect_each is similar to _array_find_each
--------------------
Last
--------------------
Running each test 16 times. Test will take about 2 seconds.
_array_include? is faster than _array_find_index by 10.000000000000009% ± 10.0%
_array_find_index is similar to _array_index
_array_index is faster than _array_member? by 3x ± 0.1
_array_member? is faster than _array_find_index_each by 2x ± 0.1
_array_find_index_each is similar to _array_index_each
_array_index_each is similar to _array_any_each
_array_any_each is faster than _array_detect_each by 30.000000000000004% ± 10.0%
_array_detect_each is similar to _array_find_each

--------------------
Sets vs. Array.include?
--------------------
--------------------
First
--------------------
Running each test 65536 times. Test will take about 1 second.
_array_include? is similar to _set_include?
_set_include? is similar to _set_member?
--------------------
Middle
--------------------
Running each test 65536 times. Test will take about 2 minutes.
_set_member? is similar to _set_include?
_set_include? is faster than _array_include? by 1400x ± 1000.0
--------------------
Last
--------------------
Running each test 65536 times. Test will take about 4 minutes.
_set_member? is similar to _set_include?
_set_include? is faster than _array_include? by 3000x ± 1000.0

Fondamentalmente i risultati mi dicono di usare un Set per tutto se cercherò l'inclusione a meno che non possa garantire che il primo elemento sia quello che voglio, il che non è molto probabile. C'è un certo sovraccarico quando si inseriscono elementi in un hash, ma i tempi di ricerca sono molto più veloci che non penso che dovrebbero mai essere presi in considerazione. Ancora una volta, se è necessario cercarlo, non utilizzare un array, utilizzare un set. (O un hash.)

Più piccola è la matrice, più veloci saranno i metodi di matrice, ma non continueranno a tenere il passo, anche se in piccoli array la differenza potrebbe essere minuscola.

"In primo luogo", "Medio" e "Ultimo" riflettono l'uso di first, size / 2e lastper ARRAYper l'essere elemento cercato. Tale elemento verrà utilizzato durante la ricerca delle variabili ARRAYe SET.

Sono state apportate piccole modifiche ai metodi a confronto > 0perché il test dovrebbe essere >= 0per i indextest di tipo.

Ulteriori informazioni su Fruttato e la sua metodologia sono disponibili nel suo README .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.