【问题标题】:Generating random poll numbers生成随机投票号码
【发布时间】:2016-01-26 20:01:56
【问题描述】:

我在为这个简单的问题而苦恼:我想创建一些随机投票号码。我有 4 个变量需要用数据填充(实际上是一个整数数组)。这些数字应该代表一个随机百分比。添加的所有百分比将为 100% 。听起来很简单。

但我认为这并不容易。我的第一次尝试是生成一个介于 10 和基数(基数 = 100)之间的随机数,然后从基数中减去该数字。这样做了 3 次,最后一个值被分配到基数。有没有更优雅的方法来做到这一点?

我的问题简述:

如何用随机值填充这个数组,加起来是 100?

int values[4];

【问题讨论】:

  • 显示您尝试过的内容。
  • 生成4个随机整数然后设置values[x] = values[x] * 100 / (sum of the 4 random integers).
  • 提示:随机数只有 3 个。 4 == 100 的总和。
  • 大多数简单的解决方案都是有偏见的。这是一个不太偏颇的简单解决方案:在 [0, 100] 范围内生成三个数字。对它们进行排序,并在最后放置 100。连续的差异就是你的数字。

标签: c random


【解决方案1】:

您需要编写代码来模拟您正在模拟的内容。

因此,如果您有四个选择,请生成一个随机数 (0..1 * 4) 的样本大小,然后将所有 0、1、2 和 3 相加(记住不会选择 4)。然后将计数除以样本大小。

for (each sample) {
   poll = random(choices);
   survey[poll] += 1;
}

用电脑模拟东西很容易,简单的模拟很快。

请记住,您使用的是整数,如果不将它们转换为浮点数或双精度数,整数就不能很好地除法。如果您遗漏了几个百分点,则很可能与您的整数除以余数有关。

【讨论】:

  • 这是最简单的可证明无偏见的解决方案;除非性能是一个问题,否则这是最有意义的问题。在 C 中,这更像是for (int i = 0; i < 100; i++) { values[rand() % 4]++; }
  • 哇,正是我搜索的内容。其实很简单:)
  • 这里的“无偏见”是相对的。该算法将产生一个均匀分布的人口的随机样本。然而,很少有人口分布均匀。例如,40-30-15-15 的分布在现实世界中是相当可能的轮询结果,但是使用这种方法生成大小为 40 的分区的概率非常小,以至于基本上无法观察到。
  • @rici 是的,为了模拟这样的采样,您需要结果的分布。在这种情况下,您可以通过它和已处理区域的总和来确定分布区域的“截断”,然后在它和人口限制之间选择一个随机数。但是,如果你有分布,那么你就不需要模拟抽样来获得分布。在这种情况下,我认为这更像是一种“练习”,而不是正在解决的现实问题。
  • @EdwinBuck:如果你想说明抽样理论,你可能还是想做抽样。一个经典的蒙特卡洛实验是在像 d'Hondt 这样的比例系统中估计议会席位分配的置信限度,假设投票结果代表真实的投票分布。计算原始样本数的置信范围很简单,但由于不连续性,最终座位分布的解析解很困难,并且实验将迅速产生包络内的概率。
【解决方案2】:

您遇到的问题是将数字 100 分成 4 个随机整数。这称为 partitioning in number theory
此问题已解决here。 那里提出的解决方案基本上执行以下操作:
如果计算,整数nO(n^2) 时间内有多少个分区。这将生成一个大小为O(n^2) 的表,然后可以使用该表在O(n) 时间为任何整数k 生成nkth 分区。
在您的情况下,n = 100k = 4

【讨论】:

    【解决方案3】:

    在 范围内生成 x1,从 1 中减去它,然后在 范围内生成 x2,依此类推。最后一个值不应该是随机的,但在你的情况下等于 1-x1-x2-x3。

    【讨论】:

    • 什么是range <0..1>
    • 100% = 1? @FiddlingBits
    • 从 0 到 1,假设 0 = 0%,1 = 100%。您可以选择 范围内的任何一个,具体取决于您觉得更好看的内容。
    【解决方案4】:

    我不认为这比你已经做过的更漂亮,但它确实有效。 (唯一的优点是如果你想要超过 4 个元素,它是可扩展的)。

    确保你#include <stdlib.h>

    int prev_sum = 0, j = 0;
    for(j = 0; j < 3; ++j)
    {
        values[j] = rand() % (100-prev_sum);
        prev_sum += values[j];
    }
    values[3] = 100 - prev_sum;
    

    【讨论】:

    • 分布不均。 values[0] 有 100 分之一变为 99。values[1] 有 9900 分之一变为 99,等等。
    【解决方案5】:

    要为“随机分区”问题找到真正公正的解决方案需要做一些工作。但首先有必要了解“无偏见”在这种情况下的含义。

    一条推理是基于随机抛硬币的直觉。一枚无偏硬币正面朝上的频率与反面朝上的频率一样多,因此我们可能会认为,我们可以通过将无偏硬币抛 100 次并计数,将 100 次抛掷分成两部分(正面计数和尾部计数)的无偏分区.这就是Edwin Buck's proposal 的精髓,经过修改以生成四分区而不是二分区。

    但是,我们会发现许多分区从未出现过。有 101 个 100 的两个分区 -- {0, 100}, {1, 99} … {100, 0} 但硬币抽样解决方案在 10,000 次尝试中发现不到一半。正如所料,分区{50, 50} 是最常见的(7.8%),而从{0, 100}{39, 61} 的所有分区总共不到1.7%(而且,在我做的试验中,分区从{0, 100}{31, 69} 根本没有出现。)[注1]

    所以这看起来不像是可能的分区的无偏样本。一个无偏的分区样本将以相等的概率返回每个分区。

    所以另一个诱惑是从所有可能的大小中选择分区的第一部分的大小,然后从剩下的任何大小中选择第二部分的大小,依此类推,直到我们达到小于分区的大小,此时剩下的任何东西都在最后一部分。然而,这也会有偏差,因为第一部分比其他任何部分都大。

    最后,我们可以枚举所有可能的分区,然后随机选择其中一个。这显然是不偏不倚的,但不幸的是有很多可能的分区。例如,对于 100 个 4 分区的情况,有 176,581 种可能性。在这种情况下也许这是可行的,但似乎不会导致通用解决方案。

    为了更好的算法,我们可以从观察分区开始

    {p<sub>1</sub>, p<sub>2</sub>, p<sub>3</sub>, p<sub>4</sub>}

    可以无偏差地重写为累积分布函数 (CDF):

    {p<sub>1</sub>, p<sub>1</sub>+p<sub>2</sub>, p<sub>1</sub>+p<sub>2</sub>+p<sub>3</sub>, p<sub>1</sub>+p<sub>2</sub>+p<sub>3</sub>+p<sub>4</sub>}

    最后一项就是所需的总和,在本例中为 100。

    那仍然是 [0, 100] 范围内的四个整数的集合;但是,它保证是按递增顺序排列的。

    生成四个以 100 结尾的随机排序序列并不容易,但是生成三个不大于 100 的随机整数,对它们进行排序,然后找到相邻的差异是很简单的。这导致了一个几乎没有偏见的解决方案,对于大多数实际目的来说,这可能已经足够接近了,特别是因为实现几乎是微不足道的:

    (Python)

    def random_partition(n, k):
      d = sorted(randrange(n+1) for i in range(k-1))
      return [b - a for a, b in zip([0] + d, d + [n])]
    

    不幸的是,由于sort,这仍然存在偏见。未排序的列表是从所有可能列表中毫无偏差地选择的,但排序步骤不是简单的一对一匹配:具有重复元素的列表比没有重复元素的列表具有更少的排列,因此特定排序列表的概率没有重复的概率远高于有重复的排序列表的概率。

    随着 n 相对于 k 变大,具有重复的列表的数量迅速下降。 (这些对应于其中一个或多个部分为 0 的最终分区。)在渐近线中,我们从连续体中进行选择并且碰撞概率为 0,算法是无偏的。即使在 n=100,k=4 的情况下,对于许多实际应用,偏差也可能是可忽略的。将 n 增加到 1000 或 10000(然后缩放生成的随机分区)将减少偏差。

    有一些快速算法可以生成无偏整数分区,但它们通常要么难以理解,要么速度慢。慢的,需要时间(n),类似于reservoir sampling;如需更快的算法,请参阅Jeffrey Vitter. 的工作


    注意事项

    1. 这是快速而简单的 Python + shell 测试:

      $ python -c '
      from random import randrange
      n = 2
      for i in range(10000):
        d = n * [0]
        for j in range(100):
          d[randrange(n)] += 1
        print(' '.join(str(f) for f in d))
      ' | sort -n | uniq -c
      
        1 32 68
        2 34 66
        5 35 65
       15 36 64
       45 37 63
       40 38 62
       66 39 61
      110 40 60
      154 41 59
      219 42 58
      309 43 57
      385 44 56
      462 45 55
      610 46 54
      648 47 53
      717 48 52
      749 49 51
      779 50 50
      788 51 49
      723 52 48
      695 53 47
      591 54 46
      498 55 45
      366 56 44
      318 57 43
      234 58 42
      174 59 41
      118 60 40
       66 61 39
       45 62 38
       22 63 37
       21 64 36
       15 65 35
        2 66 34
        4 67 33
        2 68 32
        1 70 30
        1 71 29
      

    【讨论】:

      【解决方案6】:

      您可以通过创建一个将数组中的数字相加的计算函数来强制执行此操作。如果不等于100,则重新生成数组中的随机值,重新计算。

      【讨论】:

      • 这会非常耗时,不是吗?
      猜你喜欢
      • 1970-01-01
      • 2022-11-16
      • 2019-07-17
      • 1970-01-01
      • 1970-01-01
      • 2019-03-27
      • 2016-02-04
      • 1970-01-01
      • 2016-10-09
      相关资源
      最近更新 更多