如何在Ruby中生成n个唯一随机数的列表?

36

这是我目前的进展:

myArray.map!{ rand(max) }

显然,有时列表中的数字不是唯一的。 如何确保我的列表只包含唯一的数字,而无需创建一个更大的列表,然后从中选择n个唯一的数字?

编辑:
如果可能的话,我真的想看到这样做没有循环。


FYI,我的答案展示了一种不需要循环就能工作的模式。 - Sam Saffron
15个回答

66
(0..50).to_a.sort{ rand() - 0.5 }[0..x] 

(minvalue..max value).to_a 可以替换为任何数组。 0 为 "minvalue",50 为 "max value", x 为 "我想要的值的数量"。

当然,x 不可能被允许大于 max-min :)

关于它是如何工作的扩展说明:

(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 ]

注释:

值得一提的是,在最初回答此问题的时候,也就是2008年9月,Array#shuffle 要么没有可用,要么我还不知道这个方法,因此我使用了 Array#sort 来近似处理。

结果有一系列建议对此进行编辑。

所以:

.sort{ rand() - 0.5 }

使用现代的Ruby实现可以更好且更短地表达。

.shuffle

此外,

[0..x]

可以使用Array#take更明显地编写:

.take(x)

因此,要在现代Ruby上生成一系列随机数的最简单方法是:

Thus, the easiest way to produce a sequence of random numbers on a modern ruby is:
(0..50).to_a.shuffle.take(x)

7
[0,1,2,3,4,5].shuffle不是更容易吗? - Federico
看看日期@Federico,2008年初的1.8.6版本中Ruby没有这样的方法http://ruby-doc.org/core-1.8.6/Array.html,据我所知是在1.8.7中添加的。http://svn.ruby-lang.org/repos/ruby/tags/v1_8_7/ChangeLog - Kent Fredric
当时可能已经发布了1.8.7版本,但我可能还没有更新它,或者还不知道.shuffle的添加。 - Kent Fredric
1
(0..50).to_a.shuffle.take(x) 太棒了!! - Juanin
@DanielRichter,当然这种方法并不能保证唯一性。 - Kent Fredric
显示剩余2条评论

25

这里使用了Set:

require 'set'

def rand_n(n, max)
    randoms = Set.new
    loop do
        randoms << rand(max)
        return randoms.to_a if randoms.size >= n
    end
end

有没有不用循环的方法来做这件事?有没有使用映射(map)的方法来做它? - Esteban Araya
假设 Ruby 的 Set 不允许插入重复项,那么 randoms 就不如 rand(max) 随机,因为你只是丢弃了“不喜欢”的数字。 - Allen
@Allen,那不正确。该算法并没有丢弃“你不喜欢”的数字。这样做的一个例子是因为某个数字大于或小于某个特定值而将其丢弃。它所做的是跳过已经包含在集合中的数字,这是使用案例的要求。这并不会使数字集合更少随机。当它有足够的数字时就停止。 - Tony
@Tony,你说得对,我当时是错误的。不确定我在2014年吸了什么东西。 - Allen

24

Ruby 1.9提供了Array#sample方法,该方法会从数组中随机选择一个或多个元素。使用#sample得到的结果不会包含相同的元素。

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

to_a.sort_by方法相比,sample方法似乎要快得多。在一个简单的场景中,我将sort_bysample进行了比较,并得到了以下结果。

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

1
相比于格伦·麦克唐纳报道的时间,你有什么想法? - Hedgehog

22

只是为了让您了解速度,我运行了以下四个版本:

  1. 使用集合(如Ryan的建议)。
  2. 使用略大于所需的数组,然后在最后进行uniq!操作。
  3. 使用哈希表(如Kyle的建议)。
  4. 创建所需大小的数组,然后随机排序它们,就像Kent的建议一样(但不包括多余的“-0.5”,它什么也不做)。

它们在小规模下都很快,因此我让它们每个创建一个由1,000,000个数字组成的列表。以下是以秒为单位的时间:

  1. 集合:628
  2. 数组+ uniq:629
  3. 哈希表:645
  4. 固定数组+排序:8

不,最后一个不是笔误。因此,如果您关心速度,并且可以接受数字是从0到任意值的整数,则我的精确代码为:

a = (0...1000000).sort_by{rand}

线性反馈移位寄存器应该在一秒内完成。 - Bryan Larsen
1
多余的“-0.5”,其实并没有什么用。你试过了吗?没有-0.5,rand()总是返回> 0,而当> 0时,a始终小于b,然后,您不是得到洗牌,而是以底层算法比较它们的顺序返回列表。在某些情况下,按给定顺序返回列表。(0..10).to_a.sort { Random.rand() } 在 http://tryruby.org/levels/1/challenges/0 上让我输入与输出相同。所以你确实需要那个-0.5或者随机本身是什么都没做。 - Kent Fredric
1
你真的在六年后回应SO线程中微不足道的细节吗?! - glenn mcdonald

4

是的,可以不使用循环和跟踪已选择的数字来完成此操作。这被称为线性反馈移位寄存器:创建无重复随机数序列


4
[*1..99].sample(4) #=> [64, 99, 29, 49]

根据Array#sample文档,
选取元素的过程是通过使用随机和唯一的索引完成的。
如果需要使用SecureRandom(它使用计算机噪声而不是伪随机数):
require 'securerandom'

[*1..99].sample(4, random: SecureRandom) #=> [2, 75, 95, 37]

2
如何尝试这个方法?不需要使用Set或Hash,就能得到独特的随机数。
x = 0
(1..100).map{|iter| x += rand(100)}.shuffle

我总觉得,与从0到10000的范围内选择100个独特数字相比,这些数字的随机性会显著降低。 - Claudiu
是的,它需要改进,数字越高,获得它的几率就越低。但肯定有一种方法可以使类似的东西工作。 - Sam Saffron

1
不要将项目添加到列表/数组中,而是将它们添加到集合中。

1
你可以使用哈希表来跟踪你已经使用过的随机数:
seen = {}
max = 100
(1..10).map { |n|
  x = rand(max)
  while (seen[x]) 
    x = rand(max)
  end
  x
}

1

如果您有一个可能的随机数有限列表(即1到100),那么肯特的解决方案是不错的。

否则,没有其他好的方法可以避免循环。问题在于,如果出现重复,您必须进行循环。我的解决方案应该是有效的,而且循环次数不应该比数组的大小多太多(即,如果您想要20个唯一的随机数,平均需要25次迭代)。尽管所需的迭代次数会随着所需数字的增加和最大值的减小而变得更糟。这是我上面的代码修改后显示给定输入需要多少次迭代:

require 'set'

def rand_n(n, max)
    randoms = Set.new
    i = 0
    loop do
        randoms << rand(max)
        break if randoms.size > n
        i += 1
    end
    puts "Took #{i} iterations for #{n} random numbers to a max of #{max}"
    return randoms.to_a
end

如果您愿意,我可以将这段代码编写得更像Array.map :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接