2010-07-27 22 views
65

Mam tablicę @horses = [], którą wypełniam losowymi końmi.Jak sprawdzić, czy moja tablica zawiera obiekt?

Jak mogę sprawdzić, czy moja tablica @horses zawiera konia, który jest już w niej (istnieje)?

Próbowałem coś takiego:

@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 

Próbowałem też z include? ale widziałem to był tylko strun. Z exists? pojawia się następujący błąd:

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

Więc pytanie brzmi, jak mogę sprawdzić, czy mój tablica ma już „konia” dołączony tak, że nie wypełnić go z tym samym koniem?

+0

To pytanie będzie duplikatem https://stackoverflow.com/questions/1529986/ruby-methods-equivalent-of-if-a-in-list-in-python jeśli to pytanie weren 't sformułowane w kategoriach Python. –

Odpowiedz

156

tablic w Ruby nie mają exists? metody. I mają include? metodę as described in docs. Coś w rodzaju:

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

powinno działać po wyjęciu z pudełka.

+1

to może być złe, ponieważ zawierają? skanuje całą tablicę i jest rzędu n O (n) – Kush

+6

Jest liniowa w czasie, przechodzi przez listę raz nad każdym elementem. Nie ma jednak sensu dziwić się o wydajność. To taki prosty przypadek. Jeśli zależy Ci na szybkim wyszukiwaniu, użyj ['Hash'] (http://www.ruby-doc.org/core-2.1.1/Hash.html) lub [' Set'] (http: //www.ruby -doc.org/stdlib-2.1.1/libdoc/set/rdoc/Set.html) z 'std-lib'. –

+2

Odwrotnie: chyba że koni.in((suggested_horses) @suggested_horses << koń ​​ koniec –

0

To ...

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

prawdopodobnie powinien być ten ...

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

#include? powinny działać, to działa na general objects nie tylko struny. Twój problem na przykład kod jest ten test:

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

(nawet przy założeniu korzystania #include?). Próbujesz wyszukać konkretny obiekt, a nie id. Tak powinno być tak:

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

ActiveRecord ma redefined operatora porównania dla obiektów spojrzeć tylko na jego stanu (nowa/stworzony) i identyfikator

1

Metoda Array's include? akceptuje dowolny obiekt, a nie tylko ciąg. To powinno działać:

@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 
3

Dlaczego nie zrobić go po prostu przez podniesienie osiem różnych numerów z 0 do Horse.count i używać, aby dostać swoje konie?

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

Ma to tę dodatkową zaletę, że nie spowoduje nieskończonej pętli, jeśli w bazie danych znajduje się mniej niż 8 koni.

Uwaga:Array#sample jest nowy do 1,9 (i najbliższych 1.8.8), więc albo uaktualnić Ruby, require 'backports' lub użyć coś jak shuffle.first(n).

1

So the question is how can I check if my array already has a "horse" included so that I don't fill it with the same horse?

Podczas odpowiedzi dotyczą pominie tablicy aby sprawdzić, czy istnieje konkretny ciąg znaków lub obiekt, który naprawdę chodzi o to źle, bo jak tablica staje się większy, wyszukiwanie będzie trwać dłużej. Zamiast tego użyj albo Hash albo Set. Oba pozwalają tylko na pojedyncze wystąpienie określonego elementu. Zestaw będzie zachowywał się bliżej tablicy, ale zezwala tylko na pojedyncze wystąpienie. Jest to podejście bardziej zapobiegawcze, które unika duplikacji ze względu na charakter pojemnika.

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 używa pary nazwa/wartość, co oznacza, że ​​wartości nie będzie jakiegokolwiek rzeczywistego użytkowania, ale nie wydaje się być trochę dodatkowej prędkości za pomocą Hash, na podstawie niektórych badań.

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 

które wyjścia:

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

Jeśli chcesz sprawdzić, czy obiekt jest w zasięgu w tablicy sprawdzając atrybut na obiekcie, można użyć any? i przekazać blok że wartość true lub false:

unless @suggested_horses.any? {|h| h.id == horse.id } 
    @suggested_horses << horse 
end 
Powiązane problemy