【问题标题】:Randomly Generate a set of numbers of n length totaling x随机生成一组长度为 n 的数字,总计 x
【发布时间】:2011-08-16 19:24:18
【问题描述】:

我正在做一个有趣的项目,我需要一个算法来执行以下操作: 生成长度为n 的数字列表,加起来为x

我会满足于整数列表,但理想情况下,我希望留下一组浮点数。

如果这个问题没有得到深入研究,我会感到非常惊讶,但我不确定要寻找什么。

我过去曾解决过类似的问题,但这个问题的性质明显不同。在我生成将加起来为 x 的数字列表的不同组合之前。我确信我可以简单地暴力破解这个问题,但这似乎不是理想的解决方案。

任何人都知道这可能被称为什么,或者如何处理它?谢谢大家!

编辑:澄清一下,我的意思是列表的长度应该是 N,而数字本身可以是任意大小。

edit2:对不起,我不恰当地使用了“set”,我将它用作列表或数组的全部术语。我知道这造成了混乱,我深表歉意。

【问题讨论】:

  • 要清楚,“长度n”是指整数的十进制表示,不带前导零,应该是n 数字长,对吧?
  • 您需要整数吗?如果没有,只需生成n 随机数,计算它们的总和并按比例缩小或放大到所需的总和。
  • @jwodder 很抱歉含糊不清,我编辑了我的问题以澄清
  • 还有stackoverflow.com/questions/3959021/… 讨论了一些微妙之处

标签: algorithm math combinatorics


【解决方案1】:

这是在 Python 中的做法

import random

def random_values_with_prescribed_sum(n, total):
    x = [random.random() for i in range(n)]
    k = total / sum(x)
    return [v * k for v in x]

基本上你选择 n 个随机数,计算它们的总和并计算一个比例因子,这样总和就是你想要的。

请注意,这种方法不会产生“均匀”的切片,即您将获得的分布往往比在具有给定总和的所有分布中随机挑选时更“平等”。

要了解原因,您可以想象一下算法在两个具有规定和的数字(例如 1)的情况下的作用:

P 是通过选择两个随机数获得的通用点,它将在正方形[0,1]x[0,1] 内均匀。点Q是对P进行缩放得到的点,所以求和为1。从图中可以看出,靠近中心的点概率较高;例如,正方形的确切中心将通过投影对角线(0,0)-(1,1) 上的任何点来找到,而点(0, 1) 将仅投影来自(0,0)-(0,1) 的点...对角线长度为sqrt(2)=1.4142... 而方边只有1.0

【讨论】:

  • 这将起作用,除非期望的总数为零(将给出一个不那么随机的输出),并且如果随机数的总和恰好非常接近于零(将爆炸或产生巨大的数字)。虽然仍然是最好的解决方案,但可能需要调整才能通用。
  • 非常感谢,非常简单。我刚刚用javascript轻松实现了它。
  • 请注意,这种分布在总和为 x 的所有 n 元组上并不均匀(即:并非所有 n 元组的可能性都相同)。奇怪的是,只要输出看起来有点随机,提问者可能不会那么在意。但是,如果他们确实关心,请参阅 this blog postDevroye's book 的第 568 页。
  • @mhum:好点。我不认为这通常是一个坏问题,因为切片实际上往往比使用统一方法“更平等”。
【解决方案2】:

实际上,您需要将 x 划分为 n 个部分。这通常通过以下方式完成:将 x 划分为 n 个非负部分可以用以下方式表示:reserve n + x 个空闲位置,在一些任意位置放置 n 个边框,其余位置放置石头。石头组加起来为x,因此可能的分区数为binomial coefficient (n + x \atop n )。

所以你的算法可以如下:选择(n + x)-set的任意n-子集,它唯一确定x 分成 n 个部分。

在 Knuth 的 TAOCP 中,第 3.4.2 章讨论了随机抽样。请参阅那里的算法 S。


算法S:(从总共N个记录中选择n个任意记录)

  1. t = 0, m = 0;
  2. u = 随机的,均匀分布在 (0, 1) 上
  3. 如果 (N - t)*u >= n - m,则跳过第 t 条记录并将 t 加 1;否则在样本中包含第 t 条记录,将 m 和 t 增加 1
  4. 如果M

非整数的解决方案在算法上很简单:您只需选择任意 n 个总和不为 0 的数字,并通过它们的总和对其进行规范。

【讨论】:

  • 我不清楚你所说的放置石头和边框是什么意思。但是感谢您提供非常深入的回答,感谢您将我指向数学,而不仅仅是代码 sn-p。
  • @Pete: 看:如果你把 n 个边界和 x 个石子排成一排,它们将 x 划分为 n 个加数,这样:如果在第一个边界和第二个边界之间有 k 个石子,它们做第一个加数k;如果在第 2 和第 3 边界之间有 m 个石头,则它们会产生第二个加数 m,以此类推。
  • @Pete:你会得到正好 n 个加数,加起来是 x
【解决方案3】:

如果您想在由x1 + x2 + ... + xN = x 定义的N-1 维空间区域内均匀采样,那么您将看到从Dirichlet distribution 采样的一种特殊情况。采样过程比为xi 生成统一偏差更复杂一些。这是使用 Python 的一种方法:

xs = [random.gammavariate(1,1) for a in range(N)]
xs = [x*v/sum(xs) for v in xs]

如果您不太关心结果的采样属性,您可以只生成统一偏差并在之后更正它们的总和。

【讨论】:

    【解决方案4】:

    这是上述算法的 Javascript 版本

        function getRandomArbitrary(min, max) {
            return Math.random() * (max - min) + min;
        };
        function getRandomArray(min, max, n) {
            var arr = [];
            for (var i = 0, l = n; i < l; i++) {
                arr.push(getRandomArbitrary(min, max))
            };
            return arr;
        };
        function randomValuesPrescribedSum(min, max, n, total) {
            var arr = getRandomArray(min, max, n);
            var sum = arr.reduce(function(pv, cv) { return pv + cv; }, 0);
            var k = total/sum;
            var delays = arr.map(function(x) { return k*x; })
            return delays;
        };
    

    你可以用

    来调用它
    var myarray = randomValuesPrescribedSum(0,1,3,3);
    

    然后用

    检查
    var sum = myarray.reduce(function(pv, cv) { return pv + cv;},0);
    

    【讨论】:

    • 晚了 3 年,但我仍然很感激答案:)
    【解决方案5】:

    这段代码做得很合理。我认为它产生的分布与 6502 的答案不同,但我不确定哪个更好或更自然。当然,他的代码更清晰/更好。

    import random
    
    def parts(total_sum, num_parts):
      points = [random.random() for i in range(num_parts-1)]
      points.append(0)
      points.append(1)
      points.sort()
    
      ret = []
      for i in range(1, len(points)):
        ret.append((points[i] - points[i-1]) * total_sum)
      return ret
    
    def test(total_sum, num_parts):
      ans = parts(total_sum, num_parts)
      assert abs(sum(ans) - total_sum) < 1e-7
      print ans
    
    test(5.5, 3)
    test(10, 1)
    test(10, 5)
    

    【讨论】:

      【解决方案6】:

      在python中:

      a:创建(随机#的0到1)次总计的列表;将 0 和总计附加到列表中

      b:对列表进行排序,测量每个元素之间的距离

      c:对列表元素进行四舍五入

      import random
      import time
      
      TOTAL       = 15
      PARTS       = 4
      PLACES      = 3
      
      def random_sum_split(parts, total, places):
      
          a = [0, total] + [random.random()*total for i in range(parts-1)]
          a.sort()
          b = [(a[i] - a[i-1]) for i in range(1, (parts+1))]
          if places == None:
              return b
          else:    
              b.pop()
              c = [round(x, places) for x in b]  
              c.append(round(total-sum(c), places))
              return c
      
      def tick():
      
          if info.tick == 1:
      
              start = time.time()
              alpha = random_sum_split(PARTS, TOTAL, PLACES)
              end = time.time()  
      
              log('alpha: %s' % alpha)
              log('total: %.7f' % sum(alpha))
              log('parts: %s' % PARTS)
              log('places: %s' % PLACES)
              log('elapsed: %.7f' % (end-start))
      

      产量:

      [2014-06-13 01:00:00] alpha: [0.154, 3.617, 6.075, 5.154]
      [2014-06-13 01:00:00] total: 15.0000000
      [2014-06-13 01:00:00] parts: 4
      [2014-06-13 01:00:00] places: 3
      [2014-06-13 01:00:00] elapsed: 0.0005839
      

      据我所知,这种分布是均匀的

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-01-25
        • 2016-10-16
        • 2017-06-17
        • 2020-03-11
        • 1970-01-01
        • 1970-01-01
        • 2018-12-16
        相关资源
        最近更新 更多