开发者

How do I check to see if my array includes an object?

开发者 https://www.devze.com 2023-01-08 20:50 出处:网络
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 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]
0

精彩评论

暂无评论...
验证码 换一张
取 消