Как проверить, содержит ли мой массив объект? - PullRequest
72 голосов
/ 27 июля 2010

У меня есть массив @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>

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

Ответы [ 7 ]

171 голосов
/ 27 июля 2010

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

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

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

13 голосов
/ 25 сентября 2015

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

unless @suggested_horses.any? {|h| h.id == horse.id }
  @suggested_horses << horse
end
3 голосов
/ 28 июля 2010

Почему бы не сделать это, просто выбрав восемь разных чисел от 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).

2 голосов
/ 27 июля 2010

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

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

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

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

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

1 голос
/ 29 июня 2013

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

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

Вместо этого используйте либо Hash , или Set .Оба допускают только один экземпляр определенного элемента.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"}>

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

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)
1 голос
/ 27 июля 2010

Метод массива 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
0 голосов
/ 27 июля 2010

Это ...

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...