Проверьте, существует ли значение в массиве в Ruby

1107

У меня есть значение 'Dog' и массив ['Cat', 'Dog', 'Bird'].

Как проверить, существует ли он в массиве без его прокрутки? Есть ли простой способ проверить, существует ли значение, не более?

  • 3
    использовать .include? метод . Возвращает логическое значение, которое вы хотите. В вашем случае просто введите: ['Cat', 'Dog', 'Bird']. Include ('Dog'), и он должен вернуть логическое значение true.
  • 0
    не использовать включить? метод, если вы хотите проверить несколько раз для различных значений, присутствующих в массиве или нет, потому что включить? каждый раз будет перебирать массив, используя операцию O (n) для поиска каждый раз, вместо этого сделайте хэш hash = arr.map {|x| [x,true]}.to_h , теперь проверьте, есть ли hash.has_key? 'Dog' возвращает истину или нет
Показать ещё 1 комментарий
Теги:
arrays

24 ответа

1745
Лучший ответ

Вы ищете include?:

>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true
  • 65
    Альтернативный синтаксис: %w(Cat Dog Bird).include? 'Dog'
  • 173
    Иногда я хотел бы, чтобы это было "содержит" не включать. Я всегда путаюсь с включениями.
Показать ещё 9 комментариев
213

Существует метод 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?...

  • 1
    Хотя в Ruby нет #in? в его ядре, если вы используете Rails, он доступен. api.rubyonrails.org/classes/Object.html#method-i-in-3F (Я знаю, что это вопрос Ruby, а не Rails, но он может помочь любому, кто хочет использовать #in? в Rails. Похоже, это было добавлено в Rails 3.1 apidock.com/rails/Object/in%3F
  • 11
    +1 за Set , часто упускаемый из виду.
158

Try

['Cat', 'Dog', 'Bird'].include?('Dog')
  • 0
    это старый синтаксис, посмотрите ответ ^^^ @ Брайана
  • 59
    @jahrichie, что именно вы считаете «старым синтаксисом» в этом ответе, необязательными скобками?
Показать ещё 1 комментарий
43

Используйте Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

Или, если сделано несколько тестов, 1 вы можете избавиться от цикла (что даже include? имеет) и перейти от O (n) к O (1) с помощью:

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


1. Я надеюсь, что это очевидно, но чтобы возражать против возражений: да, всего лишь несколько поисков, Hash [] и transpose ops доминируют в профиле и каждый из них O (n).
42

Если вы хотите проверить блок, вы можете попробовать любой? или все?.

%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
Мое вдохновение пришло отсюда: https://stackoverflow.com/questions/10342653/evaluate-if-array-has-any-items-in-ruby

  • 1
    Очень полезно, если вы хотите проверить, что любая / все эти строки включены в другую строку / константу
30

В нескольких ответах предлагается 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
  • 7
    a.each do ... end Umm ... не уверен, что это не петля
  • 0
    Этот цикл выполняется только один раз, когда создается три. Это постоянный термин, который не влияет на алгоритмическую сложность. Грубо говоря, алгоритм должен хотя бы один раз пройтись по массиву, чтобы узнать, какие слова вообще есть. После того как дерево построено, его можно использовать много раз, чтобы проверить наличие слова с приблизительно логарифмической сложностью.
Показать ещё 5 комментариев
27

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
...
  • 0
    Как нагло говорить, что у Руби ровно 11 способов что-либо сделать! Как только вы скажете, что кто-то укажет, что вы пропустили № 12, затем № 13 и так далее. Чтобы высказать свою точку зрения, я предложу другие пути, но сначала позвольте мне поставить под сомнение 11 способов, которые вы перечислили. Во-первых, вы вряд ли можете считать index и find_index (или find и detect ) как отдельные методы, так как это просто разные имена для одного и того же метода. Во-вторых, все выражения, заканчивающиеся на > 0 , неверны, что, я уверен, было недосмотром. (Продолжение).
  • 0
    ... arr.index(e) , например, возвращает 0 если arr[0] == e . Вы помните, что arr.index(e) возвращает nil если e нет. index не может использоваться, однако, если кто-то ищет nil в arr . (Та же проблема с rindex , которого нет в списке.) Преобразование массива в набор, а затем использование методов набора немного растянуто. Почему бы тогда не преобразовать в хеш (с ключами из массива и произвольными значениями), а затем использовать методы хеша? Даже если преобразование в набор в порядке, существуют другие методы набора, которые можно использовать, например !arr.to_set.add?(e) . (Продолжение).
Показать ещё 1 комментарий
16

Если вы не хотите цитировать, нет способа сделать это с помощью массивов. Вместо этого вы должны использовать 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)
  • 0
    Если вы используете функцию обнаружения, то вы можете хотя бы уменьшить зацикливание. Функция обнаружения остановится на первом «обнаруженном» элементе (блок, переданный для элемента, оценивается как истинный). Кроме того, вы можете указать обнаруживать, что делать, если ничего не обнаружено (вы можете передать лямбду).
  • 1
    @aenw не include? остановить при первом ударе?
Показать ещё 1 комментарий
15

Это еще один способ сделать это: использовать индексный метод 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'.

  • 0
    index прежнему перебирает массив, он просто возвращает значение элемента.
8

Существует несколько способов сделать это. Некоторые из них выглядят следующим образом:

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

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false
  • 6
    Обратите внимание, что Object#in? был добавлен только в Rails (т.е. ActiveSupport ) v3.1 +. Он недоступен в ядре Ruby.
7

Забавный факт,

Вы можете использовать * для проверки принадлежности массива в выражениях case.

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

Обратите внимание на небольшое * в предложении when, это проверяет принадлежность к массиву.

Все обычное магическое поведение оператора splat применяется, например, если array на самом деле не массив, а один элемент, он будет соответствовать этому элементу.

  • 0
    Хорошо знать..!
5

Это скажет вам не только, что оно существует, но и сколько раз оно появляется:

 a = ['Cat', 'Dog', 'Bird']
 a.count("Dog")
 #=> 1
  • 11
    Нет смысла использовать это, если вы не хотите знать, сколько раз оно появляется, как .any? вернется, как только найдет первый соответствующий элемент, .count всегда обработает весь массив.
5

Для чего это стоит, Ruby docs - удивительный ресурс для таких вопросов.

Я также хотел бы отметить длину массива, который вы просматриваете. Метод include? будет запускать линейный поиск с сложностью O (n), который может стать довольно уродливым в зависимости от размера массива.

Если вы работаете с большим (отсортированным) массивом, я бы подумал о написании алгоритма поиска который не должен быть слишком сложный и имеет худший случай O (log n).

Или, если вы используете Ruby 2.0, вы можете воспользоваться bsearch.

  • 3
    Бинарный поиск предполагает, что массив отсортирован (или упорядочен в некоторой форме), что может быть дорогостоящим для больших массивов, часто сводя на нет преимущество.
  • 0
    Бинарный поиск также требует, чтобы все пары элементов были сопоставимы с <=> , что не всегда так. Предположим, например, что элементы массива были хешами.
Показать ещё 1 комментарий
4

Если вы имеете в виду больше значений... вы можете попробовать:

Пример: если 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')

Примечание: член? и включать? одинаковы.

Это может сделать работу в одной строке!

3

Если вам нужно проверить кратные значения для любого ключа, преобразуйте 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? Это хорошо

  • 0
    Вы до сих пор не просматриваете массив, чтобы преобразовать его в хеш?
  • 0
    да, я сделал, но теперь у меня есть предварительно вычисленный ответ, чтобы проверить, существует ли какой-либо элемент в массиве или нет. Теперь, в следующий раз, когда я буду искать другое слово, для запроса потребуется O (1), верно, хотя предварительное вычисление потребует O (n). На самом деле я использовал включить? в моем приложении внутри цикла для проверки того, существует ли конкретный элемент в массиве или нет, это снижает производительность, он проверял в ruby-prof, что было узким местом
Показать ещё 2 комментария
3
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true
  • 0
    ['Cat', nil, 'Dog'].detect { |x| x == nil } #=> nil . Был ли найден nil ?
3

Если мы не хотим использовать include?, это также работает:

['cat','dog','horse'].select{ |x| x == 'dog' }.any?
  • 3
    любой? также принимает блоки: ['cat', 'dog', 'horse']. any? {| x | x == 'собака'}
3

Там тоже наоборот!

Предположим, что массив [: 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}[,|.| |]/}
  • 6
    .......но почему?
  • 0
    Ваше регулярное выражение, /[,|.| |]#{v.to_s}[,|.| |]/ , заставляет меня думать, что вы хотели найти «имя действия, окруженное одним из: запятая, точка, пробел или вообще ничего», но есть некоторые тонкие ошибки. "|update|" вернет [:update] и "update" вернет [] . Классы символов ( [...] ) не используют каналы ( | ) для разделения символов. Даже если мы изменим их на группы ( (...) ), вы не сможете найти пустой символ. Итак, регулярное выражение, которое вы, вероятно, хотели, это /(,|\.| |^)#{v.to_s}(,|\.| |$)/
Показать ещё 2 комментария
2

Как насчет этого пути?

['Cat', 'Dog', 'Bird'].index('Dog')
  • 0
    Он все еще будет перебирать массив, чтобы найти элемент. Затем он должен вернуть индекс этого элемента.
0

Вы должны попробовать включить метод массива

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

Чтобы узнать больше о методах массива, перейдите по ссылке: https://ruby-doc.org/core-2.2.0/Array.html.

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

Вот еще один способ сделать это:

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

present = arr.size != (arr - [e]).size
  • 1
    Это ужасно неэффективный способ сделать это! Я не опровергаю, потому что это технически некорректно, и кто-то может узнать что-то о Ruby, прочитав его, но есть много лучших ответов выше.
  • 0
    Conehead, вы можете упростить до arr != arr - [e] . arr & [e] == [e] - другой путь в том же духе.
Показать ещё 2 комментария
0

если вы не хотите использовать include? вы можете сначала обернуть элемент в массив, а затем проверить, равен ли обернутый элемент пересечению массива и обернутого элемента. Это вернет логическое значение, основанное на равенстве.

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

у него есть много способов найти элемент в любом массиве, но самый простой способ - "в?" метод.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню