How do I generate a list of n unique random numbers in Ruby?

后端 未结 15 1790
鱼传尺愫
鱼传尺愫 2020-11-28 22:27

This is what I have so far:

myArray.map!{ rand(max) }

Obviously, however, sometimes the numbers in the list are not unique. How can I mak

相关标签:
15条回答
  • 2020-11-28 23:02

    Rather than add the items to a list/array, add them to a Set.

    0 讨论(0)
  • 2020-11-28 23:06
    (0..50).to_a.sort{ rand() - 0.5 }[0..x] 
    

    (0..50).to_a can be replaced with any array. 0 is "minvalue", 50 is "max value" x is "how many values i want out"

    of course, its impossible for x to be permitted to be greater than max-min :)

    In expansion of how this works

    (0..5).to_a  ==> [0,1,2,3,4,5]
    [0,1,2,3,4,5].sort{ -1 }  ==>  [0, 1, 2, 4, 3, 5]  # constant
    [0,1,2,3,4,5].sort{  1 }  ==>  [5, 3, 0, 4, 2, 1]  # constant
    [0,1,2,3,4,5].sort{ rand() - 0.5 }   ==>  [1, 5, 0, 3, 4, 2 ]  # random
    [1, 5, 0, 3, 4, 2 ][ 0..2 ]   ==>  [1, 5, 0 ]
    

    Footnotes:

    It is worth mentioning that at the time this question was originally answered, September 2008, that Array#shuffle was either not available or not already known to me, hence the approximation in Array#sort

    And there's a barrage of suggested edits to this as a result.

    So:

    .sort{ rand() - 0.5 }
    

    Can be better, and shorter expressed on modern ruby implementations using

    .shuffle
    

    Additionally,

    [0..x]
    

    Can be more obviously written with Array#take as:

    .take(x)
    

    Thus, the easiest way to produce a sequence of random numbers on a modern ruby is:

    (0..50).to_a.shuffle.take(x)
    
    0 讨论(0)
  • Just to give you an idea about speed, I ran four versions of this:

    1. Using Sets, like Ryan's suggestion.
    2. Using an Array slightly larger than necessary, then doing uniq! at the end.
    3. Using a Hash, like Kyle suggested.
    4. Creating an Array of the required size, then sorting it randomly, like Kent's suggestion (but without the extraneous "- 0.5", which does nothing).

    They're all fast at small scales, so I had them each create a list of 1,000,000 numbers. Here are the times, in seconds:

    1. Sets: 628
    2. Array + uniq: 629
    3. Hash: 645
    4. fixed Array + sort: 8

    And no, that last one is not a typo. So if you care about speed, and it's OK for the numbers to be integers from 0 to whatever, then my exact code was:

    a = (0...1000000).sort_by{rand}
    
    0 讨论(0)
  • 2020-11-28 23:06

    Ruby 1.9 offers the Array#sample method which returns an element, or elements randomly selected from an Array. The results of #sample won't include the same Array element twice.

    (1..999).to_a.sample 5 # => [389, 30, 326, 946, 746]
    

    When compared to the to_a.sort_by approach, the sample method appears to be significantly faster. In a simple scenario I compared sort_by to sample, and got the following results.

    require 'benchmark'
    range = 0...1000000
    how_many = 5
    
    Benchmark.realtime do
      range.to_a.sample(how_many)
    end
    => 0.081083
    
    Benchmark.realtime do
      (range).sort_by{rand}[0...how_many]
    end
    => 2.907445
    
    0 讨论(0)
  • 2020-11-28 23:13

    Yes, it's possible to do this without a loop and without keeping track of which numbers have been chosen. It's called a Linear Feedback Shift Register: Create Random Number Sequence with No Repeats

    0 讨论(0)
  • 2020-11-28 23:13

    Based on Kent Fredric's solution above, this is what I ended up using:

    def n_unique_rand(number_to_generate, rand_upper_limit)
      return (0..rand_upper_limit - 1).sort_by{rand}[0..number_to_generate - 1]
    end
    

    Thanks Kent.

    0 讨论(0)
提交回复
热议问题