списки - to_a ruby




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

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? является предпочтительным способом. Он использует внутренний язык C for внутреннего цикла, который прерывается, когда элемент соответствует внутренним 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
...

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

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


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

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

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

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

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

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

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


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

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? Это хорошо


Если вы не хотите зацикливаться, нет способа сделать это с помощью массивов. Вместо этого вы должны использовать 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)

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

%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://.com/a/10342734/576497


Есть in? метод в ActiveSupport (часть Rails) с версии v3.1, как указано @campaterson. Поэтому в Rails или если вам require 'active_support' , вы можете написать:

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

OTOH, нет оператора или #in? метод в самом Ruby, хотя он был предложен ранее, в частности, Yusuke Endoh - самый высокий член рубинового ядра.

Как указывалось другими, обратный метод include? существует для всех Enumerable s, включая 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

Быстрый тест показывает, что вызов include? на 10-элементном Set примерно на 3,5 раза быстрее, чем называть его эквивалентным Array (если элемент не найден).

Заключительное заключительное примечание: будьте осторожны при использовании include? на Range есть тонкости, поэтому обратитесь к документу и сравните с cover? ...


И наоборот, тоже!

Предположим, что массив [: 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}[,|.| |]/}

Использовать 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) .


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

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

Пытаться

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

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

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

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false

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

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

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

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

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




arrays