在2个或更多范围之间随机选择

时间:2016-12-11 13:04:33

标签: ruby random

我试图制作一个随机数字生成器,它会选择更多"均匀地#34; 3到4位数范围之间。如果我这样做:

rand_3_digit_num = (100..999)
rand_4_digit_num = (1000..9999)

rand([rand_3_digit_num, rand_4_digit_num].sample)

我很清楚,在大多数情况下,会选择一个4位数字。我希望给3位数字提供更多被选中的机会,所以我这样做了:

data=

还有其他办法吗?我的目标是让3位数字的数字比普通兰特更大。如果我引入5位数字或6位数字,这个问题会变得更糟,3位数或4位数字被迅速选中的可能性会减少。

5 个答案:

答案 0 :(得分:2)

Brute solution:

list = (100..999).to_a*10 + (1000..9999).to_a
=> [100, ..., 9999]
list.size
=> 18000
list.count { |e| e < 1000 }
=> 9000
list.count { |e| 999 < e && e < 10000 }
=> 9000

现在list.sample应该给出3位和4位数字的相等概率。

答案 1 :(得分:1)

首先指定范围内的概率分布,例如:

range_prob = { (100..999)     => 0.2,
               (1000..9999)   => 0.5,
               (10000..43562) => 0.3 }

鉴于这些概率,可以随机选择范围:

def select_random_range(range_prob)
  rnd_cum_prob = rand
  cum_prob = 0.0
  range_prob.each_with_object({}) do |(rng, prob),h|
    cum_prob += prob
    h[rng] = cum_prob
  end.find { |rng, cum_prob| rnd_cum_prob <= cum_prob }.first
end

我在这里做的是从离散概率密度函数(“pdf”)range_prob构造累积分布函数(“cdf”)。 (参见下图。)为了获得随机变量,我们生成一个介于0和1之间的伪随机数,在垂直轴上绘制,确定水平线与cdf相交的位置,并在水平轴上选择相关值。 / p>

cdf

对于上面的range_prob

select_random_range(range_prob)      #=> 10000..43562 
select_random_range(range_prob)      #=> 100..999
select_random_range(range_prob)      #=> 1000..9999
select_random_range(range_prob)      #=> 100..999
select_random_range(range_prob)      #=> 10000..43562

在随机范围内选择随机值是一个很小的附加步骤。

rand select_random_range(range_prob) #=> 6467 
rand select_random_range(range_prob) #=> 16689 
rand select_random_range(range_prob) #=> 2282 
rand select_random_range(range_prob) #=> 1317 
rand select_random_range(range_prob) #=> 9015 

请参阅Kernel#rand

答案 2 :(得分:0)

我认为你的想法很好。您想要实现的是找到均匀随机N,其中N表示数字中的位数,然后找到长度为N的随机数。

您可以将其拆分为两个函数:

randomSelection(lengths):
  K = A random number from the array lengths
  return randomNumberForLength(K)

randomNumberForLength(K):
  lower_bound = 10^K
  upper_bound = 10^(K+1) - 1
  return rand(lower_bound, upper_bound)

如果您想在100 - 9999之间找到一个随机数给两个长度和三个长度的概率相等,您只需拨打randomSelection([2,3])

答案 3 :(得分:0)

这完全取决于你想如何偏向结果。例如,如果你想要一个偶数的机会获得三位或四位数字,你可以使用像(伪代码)这样​​简单的东西:

def getRand():
    if rand(0, 1) == 0:        // assume inclusive both ends.
        return rand(100, 999)
    return rand(1000, 9999)

虽然你两次调用rand的事实可能会填补真正随机要求的分布,但对于大多数用途来说,这可能已经足够了。

要在调用中执行此操作,因此可能会保留分布,您只需映射值:

def getRand():
    num = rand(1000, 18999)
    if num > 9999:
        num = (num - 10000) % 900 + 100

这将生成两个大小相等的组1000-999910000-18999,并将上层组中的值映射为100-999(因此同样可能会得到三个或四个-digit number):

10000 - 10899  ->  100 - 999
10900 - 11799  ->  100 - 999
11800 - 12699  ->  100 - 999
12700 - 13599  ->  100 - 999
13600 - 14499  ->  100 - 999
14500 - 15399  ->  100 - 999
15400 - 16299  ->  100 - 999
16300 - 17199  ->  100 - 999
17200 - 18099  ->  100 - 999
18100 - 18999  ->  100 - 999

毫无疑问,其他方法可以做到,但这一切都取决于所需的分布。

答案 4 :(得分:0)

对于您描述的问题,您的解决方案已经足够好了。

不过,

999的出现次数是1000的10倍。如果要在范围之间进行更平滑的过渡,可以使用:

# Defines a distribution for random numbers between min and max.
# Smaller numbers have a higher probably to appear.
class BiasedGenerator
  def initialize(min, max)
    @range = (Math.log(min)..Math.log(max))
  end

  def self.digit_range(min_digit, max_digit)
    new(10**(min_digit - 1), 10**max_digit - 1)
  end

  def rand
    Math.exp(Kernel.rand(@range)).round
  end
end

您只需要初始化一次:

generator = BiasedGenerator.digit_range(3, 4)

并根据需要多次使用generator.rand

random_numbers = (1..1_000_000).map do
  generator.rand
end

puts 'Min :'
puts random_numbers.min
puts 'Max :'
puts random_numbers.max
puts
random_numbers.group_by { |n| n.to_s.size }.sort_by(&:first).each do |digits, numbers|
  puts "#{digits} digits : #{numbers.size}"
end

输出:

Min :
100
Max :
9999

3 digits : 500061
4 digits : 499939

分布如下: Millions values with bias

介于100和999之间的绿色区域应与1000和9999之间的绿色区域几乎相同。

你的发电机也有这个属性:

enter image description here

为了进行比较,这里是Kernel.rand

enter image description here

使用BiasedGenerator.digit_range(3, 6)

Min :
100
Max :
999998

3 digits : 250342
4 digits : 250714
5 digits : 249814
6 digits : 249130