I have an array @horses = []
that I fill with some random horses.
How can I check if my @horses
array includes a horse that is already included (exists) in it?
I tried something like:
@suggested_horses = []
@suggested_horses << Horse.find(:first,:offset=>rand(Horse.count))
while @suggested_horses.length < 8
horse = Horse.find(:first,:offset=>rand(H开发者_运维知识库orse.count))
unless @suggested_horses.exists?(horse.id)
@suggested_horses<< horse
end
end
I also tried with include?
but I saw it was for strings only. With exists?
I get the following error:
undefined method `exists?' for #<Array:0xc11c0b8>
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?
Arrays in Ruby don't have exists?
method, but they have an include?
method as described in the docs.
Something like
unless @suggested_horses.include?(horse)
@suggested_horses << horse
end
should work out of box.
If you want to check if an object is within in array by checking an attribute on the object, you can use any?
and pass a block that evaluates to true or false:
unless @suggested_horses.any? {|h| h.id == horse.id }
@suggested_horses << horse
end
#include?
should work, it works for general objects, not only strings. Your problem in example code is this test:
unless @suggested_horses.exists?(horse.id)
@suggested_horses<< horse
end
(even assuming using #include?
). You try to search for specific object, not for id. So it should be like this:
unless @suggested_horses.include?(horse)
@suggested_horses << horse
end
ActiveRecord has redefined comparision operator for objects to take a look only for its state (new/created) and id
Why not do it simply by picking eight different numbers from 0
to Horse.count
and use that to get your horses?
offsets = (0...Horse.count).to_a.sample(8)
@suggested_horses = offsets.map{|i| Horse.first(:offset => i) }
This has the added advantage that it won't cause an infinite loop if you happen to have less than 8 horses in your database.
Note: Array#sample
is new to 1.9 (and coming in 1.8.8), so either upgrade your Ruby, require 'backports'
or use something like shuffle.first(n)
.
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?
While the answers are concerned with looking through the array to see if a particular string or object exists, that's really going about it wrong, because, as the array gets larger, the search will take longer.
Instead, use either a Hash, or a Set. Both only allow a single instance of a particular element. Set will behave closer to an Array but only allows a single instance. This is a more preemptive approach which avoids duplication because of the nature of the container.
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 uses name/value pairs, which means the values won't be of any real use, but there seems to be a little bit of extra speed using a Hash, based on some tests.
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
Which outputs:
user system total real
Hash 8.140000 0.130000 8.270000 ( 8.279462)
Array 10.680000 0.120000 10.800000 ( 10.813385)
Array's include?
method accepts any object, not just a string. This should work:
@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
Array#include?
documentation
An alternative to horses.include? new_horse
is new_horse.in? horses
.
I don't think there's much (any?) difference under the hood, but sometimes it reads better that way. Particularly if you want the first term to explain a bit more of what is going on before reading the entire statement. For example, this:
[403, 404, 503].include? http_status
...is not as readable as:
http_status.in? [403, 404, 503]
精彩评论