Как проверить, содержит ли мой массив объект?

У меня есть массив @horses = [], который я заполняю некоторыми случайными лошадьми.

Как проверить, содержит ли мой массив @horses лошадь, которая уже включена (существует) в ней?

Я пробовал что-то вроде:

@suggested_horses = []
  @suggested_horses << Horse.find(:first,:offset=>rand(Horse.count))
  while @suggested_horses.length < 8
    horse = Horse.find(:first,:offset=>rand(Horse.count))
    unless @suggested_horses.exists?(horse.id)
       @suggested_horses<< horse
    end
  end

Я также пробовал с include?, но я видел, что это было только для строк. С exists? я получаю следующую ошибку:

undefined method `exists?' for #<Array:0xc11c0b8>

Итак, вопрос в том, как я могу проверить, включен ли в моем массиве "лошадь", чтобы я не заполнял его той же лошадью?

Ответ 1

Массивы в Ruby не имеют метода exists?. И они получили include? метод как описано в docs. Что-то вроде

unless @suggested_horses.include?(horse)
   @suggested_horses << horse
end

должен работать из коробки.

Ответ 2

Если вы хотите проверить, находится ли объект внутри массива, проверяя атрибут объекта, вы можете использовать any? и передать блок, который оценивает значение true или false:

unless @suggested_horses.any? {|h| h.id == horse.id }
  @suggested_horses << horse
end

Ответ 3

Почему бы просто не выбрать восемь разных чисел от 0 до Horse.count и использовать их для получения ваших лошадей?

offsets = (0...Horse.count).to_a.sample(8)
@suggested_horses = offsets.map{|i| Horse.first(:offset => i) }

У этого есть дополнительное преимущество, что он не вызовет бесконечный цикл, если в вашей базе данных будет меньше 8 лошадей.

Примечание: Array#sample является новым для 1.9 (и в 1.8.8), поэтому либо обновите Ruby, require 'backports', либо используйте что-то вроде shuffle.first(n).

Ответ 4

#include? должен работать, он работает для общих объектов, а не только строк. Ваша проблема в примере кода - это тест:

unless @suggested_horses.exists?(horse.id)
  @suggested_horses<< horse
end

(даже если использовать #include?). Вы пытаетесь выполнить поиск определенного объекта, а не идентификатора. Так оно и должно быть:

unless @suggested_horses.include?(horse)
  @suggested_horses << horse
end

ActiveRecord имеет redefined оператор сравнения для объектов, чтобы посмотреть только его состояние (новое/созданное) и id

Ответ 5

Метод Array include? принимает любой объект, а не только строку. Это должно работать:

@suggested_horses = [] 
@suggested_horses << Horse.first(:offset => rand(Horse.count)) 
while @suggested_horses.length < 8 
  horse = Horse.first(:offset => rand(Horse.count)) 
  @suggested_horses << horse unless @suggested_horses.include?(horse)
end

Ответ 6

Итак, вопрос в том, как я могу проверить, включен ли в моем массиве "лошадь", чтобы я не заполнял его той же лошадью?

В то время как ответы связаны с просмотром массива, чтобы увидеть, существует ли конкретная строка или объект, действительно ли это происходит неправильно, потому что по мере увеличения массива поиск займет больше времени.

Вместо этого используйте либо Hash, либо Set. Оба позволяют только один экземпляр определенного элемента. Набор будет вести себя ближе к массиву, но допускает только один экземпляр. Это более превентивный подход, который позволяет избежать дублирования из-за характера контейнера.

hash = {}
hash['a'] = nil
hash['b'] = nil
hash # => {"a"=>nil, "b"=>nil}
hash['a'] = nil
hash # => {"a"=>nil, "b"=>nil}

require 'set'
ary = [].to_set
ary << 'a'
ary << 'b'
ary # => #<Set: {"a", "b"}>
ary << 'a'
ary # => #<Set: {"a", "b"}>

В Hash используются пары имя/значение, что означает, что значения не будут иметь реального использования, но, похоже, немного больше скорости, используя Hash, на основе некоторых тестов.

require 'benchmark'
require 'set'

ALPHABET = ('a' .. 'z').to_a
N = 100_000
Benchmark.bm(5) do |x|
  x.report('Hash') { 
    N.times {
      h = {}
      ALPHABET.each { |i|
        h[i] = nil
      }
    }
  }

  x.report('Array') {
    N.times {
      a = Set.new
      ALPHABET.each { |i|
        a << i
      }
    }
  }
end

Какие выходы:

            user     system      total        real
Hash    8.140000   0.130000   8.270000 (  8.279462)
Array  10.680000   0.120000  10.800000 ( 10.813385)

Ответ 7

Это...

horse = Horse.find(:first,:offset=>rand(Horse.count))
unless @suggested_horses.exists?(horse.id)
   @suggested_horses<< horse
end

Возможно, это будет...

horse = Horse.find(:first,:offset=>rand(Horse.count))
unless @suggested_horses.include?(horse)
   @suggested_horses<< horse
end