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 in
operator 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 Enumerable
s 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 Set
is 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 true
ish waarde als het element aanwezig is.
include?
is de voorkeursmethode. Het maakt gebruik van een C-Taal for
Loop Intern die breekt wanneer een element overeenkomt met de interne rb_equal_opt/rb_equal
FUNCTIES. 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 array
klasse en gebruikt een niet-geopende implementatie uit de Enumerable
module 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. Set
wordt niet geïmplementeerd in C, maar als gewone Ruby-klasse, nog steeds de O (1) toegangstijd van de onderliggende @hash
maakt 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 @hash
exemplaar, kaarten alle objecten naar true
en 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-trie
In 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 array
bijvoorbeeld 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 arr
naar hash
en 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")