【问题标题】:Set random numbers in array with spacing在数组中设置带间距的随机数
【发布时间】:2015-10-16 14:39:32
【问题描述】:

我需要用 N 个随机数填充一个 Ruby 数组。但是,我需要确保它们彼此相隔 X 个数字。

例如,我需要用 5 个数字填充一个数组,这些数字在 1 到 100 之间随机生成,但要确保没有数字比 3 更接近。

所以这是有效的:

[1, 4, 7, 22, 84]  # the differences between the numbers are >= 3

这是无效的:

[1, 2, 50, 70, 90] # "1" and "2" are too close together

有什么想法吗?

【问题讨论】:

  • 必须是递增序列吗?只有连续的数字重要吗? [1, 4, 1, 4, 1] 可以吗(如果它是随机生成的?
  • 在这种情况下什么是随机序列?我可以想出两种方法在 1 和 N 之间绘制一组随机的 M 数字,这样没有两个数字在彼此的 D 内。第一种是枚举所有这样的数字集,然后随机选择一个。 (取决于 N、M 和 D,可能会有很多组。)第二种方法是随机抽取 N 个数字,然后检查是否有任何一对在彼此的 D 范围内。如果没有,你有一个随机集;如果是,则丢弃该集合并抽取另一个随机集合 N,继续直到一个集合通过测试。 (续...)
  • (...cont.) 同样,这可能需要相当长的时间。 (这完全取决于三个参数的值。)这两种方法都会导致绘制随机集。但是,如果这两种方法在计算上都不可行,并且您采用一些 ad hoc 程序,您可能不会随机抽取一个集合,并且不知道您的样本可能有多倾斜。跨度>

标签: arrays ruby random


【解决方案1】:

这里有一个想法:每次生成随机数时,从候选者中删除每个接近它的数字:

def gen_rand_arr(arr, diff)
  sample = arr.sample
  arr.delete_if {|x| (x - sample).abs < diff }
  sample
end

然后像这样使用它:

arr = (1..100).to_a
result = []
1.upto(5) { result << gen_rand_arr(arr, 3) }
p result.sort

【讨论】:

  • 应该是&lt; diff,而不是&lt;= diff。或者你可以使用keep_if&gt;= diff
  • 请注意,您的候选数组必须足够大才能保证 5 个结果,在这种情况下至少为 1..21(如果我没记错的话)。
  • @Stefan 你是对的,当前代码不检查输入的有效性。在实际中使用它应该更具防御性,但是我在这里保持简单,只是展示一下这个想法。
【解决方案2】:
i = 1
while i < 100  do
   final = rand(i+3..i+20)
   (a ||= []).push(final)
   i +=20
end
p a

一些随机的例子。

[18, 39, 48, 65, 83]
[9, 27, 56, 66, 100]
[10, 29, 46, 68, 86]
[11, 34, 57, 64, 86]
[3, 31, 46, 70, 99]
[16, 36, 43, 75, 92]

【讨论】:

  • 您正在向序列添加额外的约束。这是理解 OP 的坏问题的一种方法。问题不清楚是 OP 的错,但我不确定这是否是 OP 的意图。
  • 但是,不要将全局变量用于此类非常临时的事情。
  • 此方法将从 4..21、24..41、44..61、64..81 和 84..101 范围内获取随机数。永远不会返回来自中间间隙的数字。
  • 暂时忘记了数字不能太接近的要求,假设我们只画了 1 到 100 之间的 5 个数字,并想计算 1-20、21 五个区间中的每一个的概率-40、41-60、61-80 和 81-100 包含一个数字。为了简化计算,我将假设带放回抽样,这与带放回抽样(多项分布)非常接近。第一个数字可以进入五个区间中的任何一个,第二个必须进入其余 4 个区间之一,概率为 4/5,(续......)
  • (...cont.) 第三个必须进入剩下的 3 个区间之一,概率为 3/5,依此类推。因此,其中有一个随机抽取的数字的概率每个区间等于 4!/5^4,约为 0.038。
【解决方案3】:

这个随机版本怎么样?

sample_result = []
arr = (1..100).to_a
diff = 15
result_len = 5
1.upto(result_len) {
    sample = arr.sample
    sample_result << sample
    (sample-diff..sample+diff).each do 
        |i| arr.delete(i)
    end
}
p sample_result.sort

【讨论】:

    【解决方案4】:

    这是解决此问题的另一种方法。

    允许的最小数字是:

    a = Array.new(5) { |i| i * 3 + 1 }
    #=> [1, 4, 7, 10, 13]
    

    我们可以将这些数字(或这组数字)递增 100 - 13 = 87 次,直到我们到达终点:

    87.times { a[0..-1] = a[0..-1].map(&:next) }
    a #=> [88, 91, 94, 97, 100]
    

    这些是允许的最大数字。

    我们可以每次选择一个随机元素并增加那个元素(并且它是正确的邻居),而不是递增所有元素:

    def spread(size, count, step)
      arr = Array.new(count) { |i| i * step + 1 }
      (size - arr.last).times do
        i = rand(0..arr.size)
        arr[i..-1] = arr[i..-1].map(&:next)
      end
      arr
    end
    
    5.times do
      p spread(100, 5, 3)
    end
    

    输出:

    [21, 42, 56, 73, 86]
    [6, 21, 45, 61, 81]
    [20, 33, 48, 63, 81]
    [12, 38, 55, 75, 90]
    [11, 29, 50, 71, 86]
    [26, 44, 64, 79, 95]
    

    不幸的是,我们必须循环多次才能生成最终值。这不仅速度慢,而且会导致分布不均匀:

    最好确定 6 个总和为 87 的随机偏移量并相应地移动元素。为什么是6?因为偏移量是我们 5 个数字之间的距离,即:

           n1       n2                 n3     n4           n5            
    |<-o1->|<--o2-->|<-------o3------->|<-o4->|<----o5---->|<----o6---->|
    0                                                                  max
    

    这个辅助方法返回这样的偏移量:(从here偷来的)

    def offsets(size, count)
      offsets = Array.new(count) { rand(0..size) }.sort
      [0, *offsets, size].each_cons(2).map { |a, b| b - a }
    end
    
    o = offsets(87, 5) #=> [3, 0, 15, 4, 64, 1]
    o.inject(:+)       #=> 87
    

    我们可以将偏移量添加到我们的初始数字数组中:

    def spread(size, count, step)
      arr = Array.new(count) { |i| i * step }
      offsets(size - arr.last, count).each_with_index do |offset, index|
        arr[index..-1] = arr[index..-1].map { |i| i + offset }
      end
      arr
    end
    
    5.times do
      p spread(99, 5, 3)
    end
    

    输出:

    [1, 14, 48, 60, 94]
    [12, 46, 54, 67, 72]
    [8, 14, 35, 40, 45]
    [27, 30, 51, 81, 94]
    [63, 79, 86, 90, 96]
    

    正如预期的那样,这会导致随机分布:

    这样看起来更好。请注意,这些结果是从零开始的。

    我们甚至可以删除初始数组并根据偏移量计算最终值。从第一个偏移量开始,我们只需将后面的每个偏移量加上 3:

    def spread(size, count, step)
      offs = offsets(size - (count - 1) * step, count)
      (1...offs.size).each { |i| offs[i] += offs[i-1] + step }
      offs[0, count]
    end
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多