Ответ 1
Вы ищете include?
:
>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
У меня есть значение 'Dog'
и массив ['Cat', 'Dog', 'Bird']
.
Как проверить, существует ли он в массиве без его прокрутки? Есть ли простой способ проверить, существует ли значение, не более?
Вы ищете include?
:
>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
Существует метод in?
в ActiveSupport
(часть Rails) с версии v3.1, как указано @campaterson. Поэтому в Rails или если вы require 'active_support'
, вы можете написать:
'Unicorn'.in?(['Cat', 'Dog', 'Bird']) # => false
OTOH, в Ruby нет оператора in
или #in?
, хотя он был предложен ранее, include?
, для всех Enumerable
включая Array
, Hash
, Set
, Range
:
['Cat', 'Dog', 'Bird'].include?('Unicorn') # => false
Обратите внимание, что если у вас много значений в вашем массиве, все они будут проверяться один за другим (т.е. O(n)
), в то время как поиск хэша будет постоянным (т.е. O(1)
). Поэтому, если массив является постоянным, например, рекомендуется использовать Set. Например:
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 быстрый тест показывает, что вызов include?
в 10 элементах Set
примерно на 3,5 раза быстрее, чем вызов его на эквивалентном Array
(если элемент не найден).
Заключительное заключительное примечание: будьте осторожны при использовании include?
на Range
, есть тонкости, поэтому обратитесь к к документу и сравните с cover?
...
Try
['Cat', 'Dog', 'Bird'].include?('Dog')
Используйте Enumerable#include
:
a = %w/Cat Dog Bird/
a.include? 'Dog'
Или, если сделано несколько тестов, 1 вы можете избавиться от цикла (что даже include?
имеет) и перейти от O (n) к O (1) с помощью:
h = Hash[[a, a].transpose]
h['Dog']
Если вы хотите проверить блок, вы можете попробовать любой? или все?.
%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
Подробности здесь: http://ruby-doc.org/core-1.9.3/Enumerable.html
Мое вдохновение пришло отсюда: fooobar.com/questions/10947/...
В нескольких ответах предлагается Array#include?
, но есть одна важная оговорка: глядя на источник, даже Array#include?
выполняет цикл:
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;
}
Способ проверки присутствия слова без цикла - это создание trie для вашего массива. Существует много реализаций trie (google "ruby trie" ). Я буду использовать rambling-trie
в этом примере:
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 }
И теперь мы готовы проверить наличие различных слов в вашем массиве, не зацикливая на нем, в O(log n)
время с той же синтаксической простотой, что и Array#include?
, используя sublinear Trie#include?
:
trie.include? 'bird' #=> true
trie.include? 'duck' #=> false
Ruby имеет 11 методов для поиска элементов в массиве.
Предпочтительным является include?
Или для повторного доступа, создания набора, а затем вызова include?
или member?
Вот все они,
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
Все они возвращают значение true
ish, если элемент присутствует.
include?
является предпочтительным методом. Он использует внутренний цикл for
C-language, который прерывается, когда элемент соответствует внутренним функциям rb_equal_opt/rb_equal
. Он не может стать намного более эффективным, если вы не создадите набор для повторных проверок членства.
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?
не переопределяется в классе Array
и использует неоптимизированную реализацию из модуля Enumerable
, который буквально перечисляет все элементы.
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;
}
Переведенный на Ruby-код, это касается следующих
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
Оба include?
и member?
имеют O(n)
временную сложность, так как оба ищут массив для первого вхождения ожидаемого значения.
Мы можем использовать набор для получения времени доступа O(1)
за счет необходимости создания хэш-представления массива в первую очередь. Если вы повторно проверяете членство в том же массиве, это первоначальные инвестиции могут быстро окупиться. Set
не реализован в C, а как обычный класс Ruby, тем не менее O(1)
время доступа базового @hash
делает это стоящим.
Вот реализация класса 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
Как вы можете видеть, класс Set
просто создает внутренний экземпляр @hash
, сопоставляет все объекты с true
и затем проверяет членство с помощью Hash#include?
, которое реализовано с O(1)
временем доступа в Hash
класс.
Я не буду обсуждать другие 7 методов, поскольку они все менее эффективны.
На самом деле существует еще больше методов с O(n)
сложностью выше 11, перечисленных выше, но я решил не перечислить их с момента сканирования всего массива, а не разрыва в первом матче.
Не используйте их,
# bad examples
array.grep(element).any?
array.select { |each| each == element }.size > 0
...
Если вы не хотите цитировать, нет способа сделать это с помощью массивов. Вместо этого вы должны использовать 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
Устанавливает работу внутри себя как хеширование, поэтому Ruby не нужно перебирать коллекцию для поиска элементов, поскольку, как следует из названия, она генерирует хэши ключей и создает карту памяти, чтобы каждая хэш-точка указывала на определенную точку в памяти. Предыдущий пример выполнен с помощью Hash:
fake_array = {}
100.times{|i| fake_array["foo#{i}"] = 1}
fake_array.has_key?("foo99")
=> true
Недостатком является то, что клавиши Sets и hash могут включать только уникальные элементы, и если вы добавите много элементов, Ruby придется перефразировать все это после определенного количества элементов, чтобы построить новую карту, которая подходит для большего пространства ключей. Для получения дополнительной информации я рекомендую вам смотреть MountainWest RubyConf 2014 - Big O в домашнем хэше от Nathan Long
Здесь контрольный показатель:
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
И результаты:
user system total real
array 7.020000 0.000000 7.020000 ( 7.031525)
set 0.010000 0.000000 0.010000 ( 0.004816)
Это еще один способ сделать это: использовать индексный метод Array #.
Он возвращает индекс первого вхождения элемента в массив.
Пример:
a = ['cat','dog','horse']
if a.index('dog')
puts "dog exists in the array"
end
index() также может принимать блок
например
a = ['cat','dog','horse']
puts a.index {|x| x.match /o/}
здесь, верните индекс первого слова в массив, содержащий букву 'o'.
Существует несколько способов сделать это. Некоторые из них выглядят следующим образом:
a = [1,2,3,4,5]
2.in? a #=> true
8.in? a #=> false
a.member? 1 #=> true
a.member? 8 #=> false
Забавный факт,
Вы можете использовать *
для проверки принадлежности массива в выражениях case
.
case element
when *array
...
else
...
end
Обратите внимание на небольшое *
в предложении when, это проверяет принадлежность к массиву.
Все обычное магическое поведение оператора splat применяется, например, если array
на самом деле не массив, а один элемент, он будет соответствовать этому элементу.
Для чего это стоит, Ruby docs - удивительный ресурс для таких вопросов.
Я также хотел бы отметить длину массива, который вы просматриваете. Метод include?
будет запускать линейный поиск с сложностью O (n), который может стать довольно уродливым в зависимости от размера массива.
Если вы работаете с большим (отсортированным) массивом, я бы подумал о написании алгоритма поиска который не должен быть слишком сложный и имеет худший случай O (log n).
Или, если вы используете Ruby 2.0, вы можете воспользоваться bsearch
.
Это скажет вам не только, что оно существует, но и сколько раз оно появляется:
a = ['Cat', 'Dog', 'Bird']
a.count("Dog")
#=> 1
Если вы имеете в виду больше значений... вы можете попробовать:
Пример: если Cat и Dog существуют в массиве:
(['Cat','Dog','Bird'] & ['Cat','Dog'] ).size == 2 #or replace 2 with ['Cat','Dog].size
Вместо:
['Cat','Dog','Bird'].member?('Cat') and ['Cat','Dog','Bird'].include?('Dog')
Примечание: член? и включать? одинаковы.
Это может сделать работу в одной строке!
Там тоже наоборот!
Предположим, что массив [: edit,: update,: create,: show] - возможно, все семь смертельных/успокоительных грехов:)
И дальше игрушка с идеей вытащить действительное действие из какой-то строки - скажем
мой брат хотел бы, чтобы я обновил его профиль
[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}
Если мы не хотим использовать include?
, это также работает:
['cat','dog','horse'].select{ |x| x == 'dog' }.any?
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
Если вам нужно проверить кратные значения для любого ключа, преобразуйте arr
в hash
, а теперь проверьте в 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
Производительность Hash # has_key? по сравнению с массивом # включить?
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
Для однократной проверки используйте include?
Это хорошо
Как насчет этого пути?
['Cat', 'Dog', 'Bird'].index('Dog')
если вы не хотите использовать include? вы можете сначала обернуть элемент в массив, а затем проверить, равен ли обернутый элемент пересечению массива и обернутого элемента. Это вернет логическое значение, основанное на равенстве.
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")
Вы должны попробовать включить метод массива
['Cat', 'Dog', 'Bird'].include? 'Dog'
>>true
Чтобы узнать больше о методах массива, перейдите по ссылке: https://ruby-doc.org/core-2.2.0/Array.html.
у него есть много способов найти элемент в любом массиве, но самый простой способ - "в?" метод.
example:
arr = [1,2,3,4]
number = 1
puts "yes #{number} is present in arr" if number.in? arr