【问题标题】:How do I generate a list of n unique random numbers in Ruby?如何在 Ruby 中生成一个包含 n 个唯一随机数的列表?
【发布时间】:2010-09-12 06:10:54
【问题描述】:

这是我目前所拥有的:

myArray.map!{ rand(max) }

但显然,有时列表中的数字并不是唯一的。如何确保我的列表只包含唯一编号,而不必创建一个更大的列表,然后从中挑选 n 个唯一编号?

编辑:
我真的很想看到这个没有循环完成 - 如果可能的话。

【问题讨论】:

  • 仅供参考,我的回答显示了一种无需循环即可工作的模式

标签: ruby random


【解决方案1】:
(0..50).to_a.sort{ rand() - 0.5 }[0..x] 

(0..50).to_a 可以替换为任何数组。 0 是“最小值”,50 是“最大值” 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)

因此,在现代红宝石上生成随机数序列的最简单方法是:

(0..50).to_a.shuffle.take(x)

【讨论】:

  • 是不是 [0,1,2,3,4,5].shuffle 更容易?
  • 呵呵呵呵 :) 我不得不说@Federico。太棒了:))
  • 看看@Federico 的日期,ruby 在 2008 年初没有这样的方法,1.8.6 ruby-doc.org/core-1.8.6/Array.html,据我所知是在 1.8.7 中添加的。 svn.ruby-lang.org/repos/ruby/tags/v1_8_7/ChangeLog
  • (1.8.7当时可能已经出,但我可能还没有更新到它,或者得知添加了.shuffle
  • (0..50).to_a.shuffle.take(x) 太棒了!!
【解决方案2】:

这使用了集合:

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

【讨论】:

  • 有什么方法可以做到这一点而无需循环?有什么办法用地图做到这一点?
  • 假设 Ruby 的 Set 不允许插入重复项,randoms 的随机性将低于rand(max),因为您只是在丢弃“您不喜欢”的数字。跨度>
  • @Allen 不正确。该算法不会丢弃“你不喜欢”的数字。一个例子是丢弃一个数字,因为它大于或小于某个值。如果它已经包含在集合中,它正在做的是跳过一个数字,这是用例的要求。这不会使这组数字变得不那么随机。当它有足够的数字时它会停止。
  • @Tony 你说得对,我不正确。不知道我在 2014 年抽什么烟。
【解决方案3】:

Ruby 1.9 提供了 Array#sample 方法,该方法返回一个元素,或从数组中随机选择的元素。 #sample 的结果不会包含两次相同的 Array 元素。

(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

【讨论】:

  • 与 glenn mcdonald 报告的时间相比,您知道时间安排吗?
【解决方案4】:

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

  1. 按照 Ryan 的建议使用 Set。
  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}

【讨论】:

  • 一个线性反馈移位寄存器应该在一秒钟内完成。
  • `无关的“- 0.5”,什么都不做`你试过了吗?如果没有 - 0.5 , rand() 总是返回 > 0,当 > 0 时,a 总是小于 b,然后不是改组,...你只需按照底层算法比较它们的顺序返回列表。在某些情况下,它会按给定的顺序返回列表。 (0..10).to­_a.sort{ Rando­m.rand() } on tryruby.org/levels/1/challenges/0 给了我输入 == 输出。所以你真的需要 - 0.5,或者随机数本身什么都不做
  • 另外,你真的在​​事后六年后回应了 SO 线程中的小问题?!
【解决方案5】:

是的,可以在没有循环且不跟踪选择了哪些数字的情况下执行此操作。它被称为线性反馈移位寄存器:Create Random Number Sequence with No Repeats

【讨论】:

    【解决方案6】:
    [*1..99].sample(4) #=> [64, 99, 29, 49]
    

    根据Array#sample docs,

    使用随机和唯一索引选择元素

    如果您需要SecureRandom(它使用计算机噪声而不是伪随机数):

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

    【讨论】:

      【解决方案7】:

      来玩一下这个怎么样?唯一的随机数,无需使用 Set 或 Hash。

      x = 0
      (1..100).map{|iter| x += rand(100)}.shuffle
      

      【讨论】:

      • 不知何故,我觉得这些数字的随机性要比从 0 到 10000 范围内选择 100 个独特的数字要小得多。
      • yerp,它需要改进,数字越高,你得到它的几率就越低。但肯定有一种方法可以使这些方法发挥作用。
      【解决方案8】:

      您可以使用哈希来跟踪您目前使用的随机数:

      seen = {}
      max = 100
      (1..10).map { |n|
        x = rand(max)
        while (seen[x]) 
          x = rand(max)
        end
        x
      }
      

      【讨论】:

        【解决方案9】:

        与其将项目添加到列表/数组中,不如将它们添加到集合中。

        【讨论】:

          【解决方案10】:

          如果您有一个可能的随机数的有限列表(即 1 到 100),那么 Kent 的解决方案很好。

          否则没有其他好方法可以做到不循环。问题是如果你得到一个重复,你必须做一个循环。我的解决方案应该是有效的,并且循环不应超过数组的大小(即,如果您想要 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 :)

          【讨论】:

            【解决方案11】:

            基于上面 Kent Fredric 的解决方案,这就是我最终使用的:

            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
            

            谢谢肯特。

            【讨论】:

              【解决方案12】:

              此方法没有循环

              Array.new(size) { rand(max) }
              
              require 'benchmark'
              max = 1000000
              size = 5
              Benchmark.realtime do
                Array.new(size) { rand(max) }
              end
              
              => 1.9114e-05 
              

              【讨论】:

                【解决方案13】:

                这是一种解决方案:

                假设您希望这些随机数介于 r_minr_max 之间。对于列表中的每个元素,生成一个随机数r,并生成list[i]=list[i-1]+r。这将为您提供单调递增的随机数,保证唯一性,前提是

                • r+list[i-1] 不会溢出
                • r > 0

                对于第一个元素,您可以使用r_min 而不是list[i-1]。完成后,您可以将列表打乱,这样元素的顺序就不会那么明显了。

                这种方法的唯一问题是当您超过r_max 并且仍然有更多元素要生成时。在这种情况下,您可以将 r_minr_max 重置为您已经计算的 2 个相邻元素,然后简单地重复该过程。这有效地在没有使用数字的区间内运行相同的算法。您可以继续执行此操作,直到填充列表为止。

                【讨论】:

                  【解决方案14】:

                  只要提前知道最大值很高兴,您可以这样做:

                  class NoLoopRand
                    def initialize(max)
                      @deck = (0..max).to_a
                    end
                  
                    def getrnd
                      return @deck.delete_at(rand(@deck.length - 1))
                    end
                  end
                  

                  你可以通过这种方式获取随机数据:

                  aRndNum = NoLoopRand.new(10)
                  puts aRndNum.getrnd
                  

                  当所有值都从牌组中耗尽时,您将获得nil

                  【讨论】:

                    【解决方案15】:

                    方法一

                    使用 Kent 的方法,可以生成一个任意长度的数组,将所有值保持在有限的范围内:

                    # Generates a random array of length n.
                    #
                    # @param n     length of the desired array
                    # @param lower minimum number in the array
                    # @param upper maximum number in the array
                    def ary_rand(n, lower, upper)
                        values_set = (lower..upper).to_a
                        repetition = n/(upper-lower+1) + 1
                        (values_set*repetition).sample n
                    end
                    

                    方法二

                    另一种可能更有效的方法是从同一个 Kent 的 another answer 修改而来的:

                    def ary_rand2(n, lower, upper)
                        v = (lower..upper).to_a
                        (0...n).map{ v[rand(v.length)] }
                    end
                    

                    输出

                    puts (ary_rand 5, 0, 9).to_s # [0, 8, 2, 5, 6] expected
                    puts (ary_rand 5, 0, 9).to_s # [7, 8, 2, 4, 3] different result for same params
                    puts (ary_rand 5, 0, 1).to_s # [0, 0, 1, 0, 1] repeated values from limited range
                    puts (ary_rand 5, 9, 0).to_s # []              no such range :)
                    

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2011-05-16
                      • 2013-04-22
                      • 2023-03-08
                      • 2014-05-15
                      • 1970-01-01
                      • 2011-06-26
                      • 2023-03-16
                      相关资源
                      最近更新 更多