Як перевірити, чи мій масив містить об’єкт?


78

У мене є масив, @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>

Тож питання полягає в тому, як я можу перевірити, чи мій масив вже включає "коня", щоб я не заповнив його тим же конем?


Це запитання було б дублікатом stackoverflow.com/questions/1529986/..., якби це питання не було сформульовано з точки зору Python.
Dave Schweisguth

Відповіді:


178

Масиви в Ruby не мають exists?методу, але вони мають include?метод, як описано в документах . Щось на зразок

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

повинен працювати нестандартно.


1
це може бути погано, оскільки включати? сканує весь масив і має порядок n O (n)
Куш,

8
Це лінійно за часом, він проходить через список один раз по кожному елементу. Немає сенсу займатися продуктивністю. Це такий простий випадок. Якщо ви дбаєте про якнайшвидше використанні пошуку або Hashабо Setз std-lib.
Томаш Кудзіло

3
І навпаки: хіба що horse.in?(@suggest_horses) @suggest_horses << кінь кінця
Фелікс Лівні

2
@Kush: Погоджено, O (n) не є ідеальним. Однак, якщо ми замикаємо, середній випадок все одно O (n);) Нарешті, ми вирішили писати на Ruby (з усіх мов ...)
Mike Rapadas

24

Якщо ви хочете перевірити, чи є об'єкт всередині масиву, перевіривши атрибут об'єкта, ви можете використати 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).


2

#include?має працювати, це працює для загальних об'єктів , а не тільки рядків. Вашою проблемою в прикладі коду є цей тест:

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

(навіть припускаючи використання #include?). Ви намагаєтесь шукати конкретний об'єкт, а не ідентифікатор. Так має бути так:

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

ActiveRecord перевизначив оператор порівняння для об’єктів, щоб дивитись лише на його стан (новий / створений) та ідентифікатор


1

include?Метод Array приймає будь-який об'єкт, а не лише рядок. Це має працювати:

@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

1

Тож питання полягає в тому, як я можу перевірити, чи мій масив вже включає "коня", щоб я не заповнив його тим же конем?

Незважаючи на те, що відповіді стосуються перегляду масиву, щоб перевірити, чи існує певний рядок чи об'єкт, це дійсно йде неправильно, оскільки, коли масив збільшується, пошук триватиме довше.

Натомість використовуйте хеш або набір . Обидва дозволяють лише один екземпляр певного елемента. 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)

0

Це ...

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.