views:

369

answers:

5

Im trying to generate n unique random numbers between 1 and max

I tried the following code but doesn't work (returns repeated numbers)

r = [ ]
n.times { v = rand(max) while r.include? v ; r << v}

Whats wrong with it? Thanks

added:

max is thousands

n is 10

A: 

I think your while r.include logic is the wrong way around. Try this:

r = [ ]
while r.length < n 
  v = rand(max)
  r << v unless r.include? v
end

Note that this will go into an infinite loop if max < n.

mopoke
Thank you mopoke!
Victor P
+2  A: 

See this question.

James Thompson
+1  A: 

No, no, don't generate randomly and then check, generate the uniq numbers and then sort randomly!

(1..max).sort_by{rand}

Or, in 1.9:

(1..max).to_a.shuffle
glenn mcdonald
...and then pick the first n numbers. I suppose this method works well if max is not big. In my case max is thousands and n is 10.
Victor P
Then `(1..max).to_a.shuffle[0,n]` should work. Much less efficient, but easier to read, IMO.
kejadlen
+1  A: 

Kernel#rand generates a pseudorandom number. This should cause concern if security is an issue.

In Rails use ActiveSupport::SecureRandom.random_number

Also both Kernel#rand and ActiveSupport::SecureRandom.random_number may return 0, you say the value must be between 1 and max.

Steve Graham
Thank you Steve, but security is not an issue in this case. However I wonder how can any algorithm generate other random than a pseudo-random number. Do you have more information on this SecureRandom class?
Victor P
Use Cosmic Background Radiation lol. Good point, I wasn't clear on that. SecureRandom also generates pseudorandom numbers, but it can use the OpenSSL random number generators or /dev/urandom, both of which are crypotgraphically secure.
Steve Graham
Apparently Kernel#rand also uses /dev/urandom if available, the Ruby docs don't mention this just that it uses a modified Mersenne Twister which caused my alarm. Then again you are also at the mercy of how well /dev/urandom is implemented in the deployment environment's OS. ActiveSupport::SecureRandom will use OpenSSL if available.
Steve Graham
A: 

I am not sure of the exact ruby syntax, but as an algorithm:

list = [1 .. 10000];

nums = [];

while (nums.length < needed) {
    nums.push( list.splice(rand() * list.length) )
}

should work fairly well, so long as your range isn't too big and won't need to waste time shuffling the entire list

Eric Strom