如何检查数组是否包含对象?

我有一个数组 @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?,但是我发现它只适用于字符串:

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

所以问题是我如何检查我的数组是否已经包含了一个“ horse”,这样我就不会用相同的 horse 来填充它了?

172549 次浏览

#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

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.

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

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)

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

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]