Hoe te controleren of een waarde bestaat in een array in Ruby

Ik heb een waarde 'Dog'en een array ['Cat', 'Dog', 'Bird'].

Hoe controleer ik of het in de array voorkomt zonder er doorheen te bladeren? Is er een eenvoudige manier om te controleren of de waarde bestaat, meer niet?


Antwoord 1, autoriteit 100%

U zoekt naar include?:

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

Antwoord 2, autoriteit 13%

Er is een in?methodein ActiveSupport(onderdeel van Rails) sinds v3.1, zoals aangegeven door @campaterson. Dus binnen Rails, of als je require 'active_support', kun je schrijven:

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

OTOH, er is geen inoperator of #in?methode in Ruby zelf, ook al is het al eerder voorgesteld, in het bijzonder door Yusuke Endoheen toplid van ruby-core.

Zoals anderen hebben opgemerkt, is de omgekeerde methode include?bestaat, voor alle Enumerables inclusief array, hash, Set, Range:

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

Merk op dat als je veel waarden in je array hebt, ze allemaal de een na de ander worden gecontroleerd (dwz O(n)), terwijl die zoekopdracht voor een hash een constante tijd is (dwz O(1)). Dus als je array constant is, is het een goed idee om een In plaats daarvaninstellen. Bijv.:

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

Een snelle testonthult dat het aanroepen van include?op een 10 element Setis ongeveer 3,5x sneller dan het aanroepen op de equivalente array(als het element niet wordt gevonden).

Een laatste slotopmerking: wees op uw hoede bij het gebruik van include?op een Range, er zijn subtiliteiten, dus raadpleeg het documenten vergelijk met cover?


Antwoord 3, autoriteit 8%

Probeer

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

Antwoord 4, autoriteit 3%

Als je per blok wilt controleren, kun je any?of all?proberen.

%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  

Zie Opsombaarvoor meer informatie.

Mijn inspiratie kwam van “evalueren of array alle items in robijn


Antwoord 5, autoriteit 3%

Gebruik Enumerable#include:

a = %w/Cat Dog Bird/
a.include? 'Dog'

Of, als er een aantal tests zijn gedaan,1kun je de lus verwijderen (die zelfs include?heeft) en gaan van O( n)naar O(1)met:

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


1. Ik hoop dat dit duidelijk is, maar om bezwaren weg te nemen: ja, voor slechts een paar keer opzoeken, domineren de Hash[] en transpose-ops het profiel en zijn ze elk O(n)zelf.


Antwoord 6, autoriteit 2%

Ruby heeft elf methoden om elementen in een array te vinden.

De voorkeur gaat uit naar include?of, voor herhaalde toegang, maak een set aan en bel vervolgens include?of member?.

Dit zijn ze allemaal:

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

Ze retourneren allemaal een trueish waarde als het element aanwezig is.

include?is de voorkeursmethode. Het maakt gebruik van een C-Taal forLoop Intern die breekt wanneer een element overeenkomt met de interne rb_equal_opt/rb_equalFUNCTIES. Het kan niet veel efficiënter worden tenzij u een set maakt voor herhaalde controles van het lidmaatschap.

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?wordt niet opnieuw gedefinieerd in de arrayklasse en gebruikt een niet-geopende implementatie uit de Enumerablemodule die letterlijk opslaat door alle elementen:

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

Vertaald naar Ruby Code Dit doet het gaat om het volgende:

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

Beide include?EN member?Have O (N) -complexiteit Sinds de allebei de array voor het eerste optreden van de verwachte waarde.

We kunnen een set gebruiken om O (1) toegangstijd te krijgen tegen de kosten van het eerst maken van een hash-vertegenwoordiging van de array. Als u het lidmaatschap op dezelfde array herhaalt, kan deze initiële investering snel betalen. Setwordt niet geïmplementeerd in C, maar als gewone Ruby-klasse, nog steeds de O (1) toegangstijd van de onderliggende @hashmaakt dit de moeite waard.

Hier is de implementatie van de setklasse:

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

Zoals je kunt zien, creëert de set-klasse gewoon een interne @hashexemplaar, kaarten alle objecten naar trueen controleert vervolgens het lidmaatschap met Hash#include?die wordt geïmplementeerd met O (1) Toegangstijd in de HASH-klasse.

Ik zal de andere zeven methoden niet bespreken, omdat ze allemaal minder efficiënt zijn.

Er zijn eigenlijk nog meer methoden met O (n) complexiteit buiten de hierboven vermelde nummer, maar ik besloot ze niet op te nemen omdat ze de hele array scannen in plaats van te breken bij de eerste wedstrijd.

Gebruik deze niet:

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

7

Verschillende antwoorden suggereren Array#include?, maar er is een belangrijke waarschuwing: kijk naar de bron, zelfs Array#include?voert Looping uit:

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

De manier om het woord aanwezigheid zonder looping te testen is door een trie voor uw array te construeren. Er zijn veel Trie-implementaties (Google “Ruby Trie”). Ik gebruik rambling-trieIn dit voorbeeld:

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 }

En nu zijn we klaar om de aanwezigheid van verschillende woorden in uw array te testen zonder eroverheen te lopen, in O(log n)tijd, met dezelfde syntactische eenvoud als Array#include?, met behulp van sublinear Trie#include?:

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

8

Als u niet wilt lijden, is er geen manier om het met arrays te doen. U moet in plaats daarvan een ingesteld gebruiken.

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

Sets werken intern als hashes, dus Ruby hoeft niet door de verzameling te bladeren om items te vinden, aangezien het, zoals de naam al aangeeft, hashes van de sleutels genereert en een geheugenkaart maakt zodat elke hash naar een bepaald punt wijst in het geheugen. Het vorige voorbeeld gedaan met een hash:

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

Het nadeel is dat Sets en Hash-sleutels alleen unieke items kunnen bevatten en als je veel items toevoegt, zal Ruby het hele ding na een bepaald aantal items opnieuw moeten hashen om een nieuwe kaart te bouwen die past bij een grotere keyspace. Voor meer informatie hierover raad ik je aan om “MountainWest RubyConf 2014 – Big O in a Homemade Hash van Nathan te bekijken Lang“.

Hier is een benchmark:

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

En de resultaten:

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

Antwoord 9

Dit is een andere manier om dit te doen: gebruik de methode Array#index.

Het retourneert de index van het eerste voorkomen van het element in de array.

Bijvoorbeeld:

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

index()kan ook een blokkering aannemen:

Bijvoorbeeld:

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

Dit retourneert de index van het eerste woord in de array dat de letter ‘o’ bevat.


Antwoord 10

Leuk weetje,

U kunt *gebruiken om het lidmaatschap van een array te controleren in een case-expressie.

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

Let op de kleine *in de when-clausule, deze controleert op lidmaatschap van de array.

Al het gebruikelijke magische gedrag van de splat-operator is van toepassing, dus als arraybijvoorbeeld niet echt een array is, maar een enkel element, zal het overeenkomen met dat element.


Antwoord 11

Er zijn meerdere manieren om dit te bereiken. Een paar daarvan zijn als volgt:

a = [1,2,3,4,5]
2.in? a  #=> true
8.in? a #=> false
a.member? 1 #=> true
a.member? 8 #=> false

Antwoord 12

Dit vertelt je niet alleen dat het bestaat, maar ook hoe vaak het voorkomt:

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

Antwoord 13

Als je meerdere keren moet controleren op een sleutel, converteer dan arrnaar hashen check nu 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

Prestaties van Hash#has_key?versus Array#include?

Parameter Hash#has_key? Matrix#include
Tijd Complexiteit O(1) operatie O(n) operatie
Toegangstype Geeft toegang tot Hash [sleutel] als het door elk element heengaat
            retourneert elke waarde dan van de array tot it
            true wordt teruggegeven aan de finds de waarde in Array
            Hash#has_key? telefoongesprek
            telefoongesprek

Voor eenmalige controle met behulp van include?is prima


Antwoord 14

Je kunt het proberen:

Voorbeeld: als Kat en Hond in de array voorkomen:

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

In plaats van:

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

Opmerking: member?en include?zijn hetzelfde.

Dit kan het werk in één regel doen!


Antwoord 15

Voor wat het waard is, de Ruby-documentenzijn een geweldige bron voor dit soort vragen.

Ik zou ook rekening houden met de lengte van de array waar je doorheen zoekt. De methode include?voert een lineaire zoekopdracht uit met O(n)-complexiteit die behoorlijk lelijk kan worden, afhankelijk van de grootte van de array.

Als je met een grote (gesorteerde) array werkt, zou ik overwegen een binair zoekalgoritme te schrijven wat niet al te moeilijk zou moeten zijn en een worst case van O(log n) heeft.

Of als u Ruby 2.0 gebruikt, kunt u profiteren van bsearch.


Antwoord 16

Als we include?niet willen gebruiken, werkt dit ook:

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

Antwoord 17

Hoe zit het op deze manier?

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

Antwoord 18

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

Antwoord 19

Als je dit probeert te doen in een MiniTesteenheidstest, kun je assert_includes. Voorbeeld:

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

Antwoord 20

Het is andersom.

Stel dat de array [ :edit, :update, :create, :show ]is, nou ja, misschien de hele zeven dodelijke/rustgevende zonden.

En speel verder met het idee om een geldige actie uit een string te trekken:

"my brother would like me to update his profile"

Dan:

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

Antwoord 21

Als u de waarde niet alleen waar of onwaar wilt retourneren, gebruikt u

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

Hiermee wordt ‘Hond’ geretourneerd als deze in de lijst voorkomt, anders nul.


Antwoord 22

als u include?niet wilt gebruiken, kunt u het element eerst in een array laten wikkelen en vervolgens controleren of het ingepakte element gelijk is aan het snijpunt van de array en het ingepakte element. Dit levert een booleaanse waarde op gebaseerd op gelijkheid.

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

23

Hier is nog een manier om dit te doen:

arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'
present = arr.size != (arr - [e]).size

24

Het heeft veel manieren om een element in elke array te vinden, maar de eenvoudigste manier is ‘in?’ methode.

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

25

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

Other episodes