【问题标题】:Weighted random number generation加权随机数生成
【发布时间】:2010-11-17 18:00:10
【问题描述】:

我想以精确的方式生成加权随机数。我可以用一个例子来解释一下:我的输入数组是 [1, 2, 3],它们的权重也是 [1, 2, 3]。在那种情况下,我希望看到 1 次,2 次 2 次,3 次 3 次。就像 3 -> 2 -> 3 -> 1 -> 3 -> 2...

我正在使用 rand() 实现随机数生成,以获得 [0, sum_of_weights) 之间的范围。 sum_of_weights = 1 + 2 + 3 = 6 对于上面的例子。我在互联网上搜索了现有的解决方案,但结果不是我想要的。有时我得到 2 多于 2 次,而序列中没有 1。它仍然加权但不完全给出我等待的次数。

我不确定下面的代码有什么问题。我应该做错事还是尝试完全不同?感谢您的回答。

int random_t (int items[], int items_weight[], int number_of_items)  
{   
    double random_weight;  
    double sum_of_weight = 0;
    int i;

    /* Calculate the sum of weights */  
    for (i = 0; i < number_of_items; i++) {
        sum_of_weight += items_weight[i];
    }

    /* Choose a random number in the range [0,1) */
    srand(time(NULL));
    double g = rand() / ( (double) RAND_MAX + 1.0 );
    random_weight = g * sum_of_weight;

    /* Find a random number wrt its weight */
    int temp_total = 0;

    for (i = 0; i < number_of_items; i++) 
    {
            temp_total += items_weight[i];

            if (random_weight < temp_total)
            {
                return items[i];
            } 
    }   
        return -1; /* Oops, we could not find a random number */
}

我也尝试了一些不同的方法(代码如下)。它适用于我的情况,但整数溢出和静态变量的广泛使用使其成为问题。

如果您在给出 NULL 之前输入了一个输入数组并继续使用它。有点类似于 strtok() 的用法。

int random_w(int *arr, int weights[], int size)
{
    int selected, i;
    int totalWeight;
    double ratio;
    static long int total;
    static long int *eachTotal = NULL;
    static int *local_arr = NULL;
    static double *weight = NULL;

    if (arr != NULL) 
        {
            free(eachTotal);
            free(weight);
            eachTotal = (long int*) calloc(size, sizeof(long));
            weight = (double*) calloc(size, sizeof(double));
            total = 0;
            totalWeight = 0;
            local_arr = arr;

            for (i = 0; i < size; i++) 
            {
                totalWeight += weights[i];
            }

            for (i = 0; i < size; i++)
            {
                weight[i] = (double)weights[i] / totalWeight;
            }
            srand(time(NULL));
        }

    while (1)
    {
        selected = rand() % size;
        ratio = (double)(eachTotal[selected])/(double)(total+1);
        if (ratio < weight[selected])
        {
            total++;
            eachTotal[selected]++;

            return local_arr[selected];
        }
    }
}

【问题讨论】:

  • “在这种情况下,我希望看到 1 次 1,2 次 2 次,3 次 3 次。” 抱歉,这不是随机的工作原理。
  • 好像和上一个问题stackoverflow.com/questions/4108528/…类似

标签: c random


【解决方案1】:

这是你想要的吗?

# Weights: one 1, two 2s, three 3s
>>> import random
>>> vals = [1] * 1 + [2] * 2 + [3] * 3
>>> random.shuffle(vals)
>>> vals
[2, 3, 1, 2, 3, 3]

编辑: 哎呀,出于某种原因,我的脑海中将 C 标记替换为 Python 标记。无论如何,我认为您想要的不是“加权”随机数生成器,而是洗牌。 This 应该有帮助。

【讨论】:

  • 我对 Python 了解不多,但据我所知,您通过将每个数字乘以其权重将元素复制到一个新数组(也是一个更大的数组),对吗?但是,我不想创建一个新数组,例如成百上千个可能导致大型最终数组的项目。我也想使用标准 C 库。
  • 我在 C 中尝试了你的建议。代码在这里:tiny.cc/deter_weighted_random。首先使用项目列表调用它,如果要使用相同的列表,则为 NULL。为了用新列表替换它,只需输入一个新项目列表及其权重,然后再次调用带有 NULL 的 random_c(...)。
  • @Deniz:Python 代码根据权重(vals = 行)构建数组,然后将其随机播放。
【解决方案2】:

当您说您没有“准确”获得每个加权值的预期值数量时,您指的是多少次运行?如果您只运行任何随机过程的六次,我不希望您能够明确地说任何事情都有效或无效。您的代码可能工作正常。尝试运行一百万次,然后检查结果。或者,也许您实际上想要 Nathon 所说的内容,即预先加权的值列表,然后您可以随机打乱它并仍然具有您正在寻找的确切权重。

【讨论】:

    【解决方案3】:

    您可以从multinomial distribution 中取样。您的随机样本域(或“桶中的球瓮”)为{1, 2, 3},观察每个样本的概率(“权重”)分别为{1/6, 2/6, 3/6}

    出于演示目的,Perl 脚本可以为您提供具有以下概率的标记球的观察列表:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    use Math::Random qw(random_multinomial);
    use Data::Dumper;
    
    my $events = 10;
    my @probabilities = qw(0.167 0.333 0.5);
    my @observations = random_multinomial($events, @probabilities);
    
    print Dumper \@observations;
    

    对于 10 个事件,单个试验将返回如下内容:

    $VAR1 = 1;
    $VAR2 = 2;
    $VAR3 = 7;
    

    这意味着您(在此单一试验中)有一个带有1 标签的事件、两个带有2 标签的事件和七个带有3 标签的事件。

    如果您重复试验,您可能会得到123 标记的事件的不同分布。

    您可以轻松地从这里构建一个列表到等效的{1, 2, 2, 3, 3, 3, 3, 3, 3, 3} 列表。

    只需随机打乱第二个列表即可获得加权的、观察到的随机数列表。

    【讨论】:

    【解决方案4】:

    如果您想让采样频率完全确定,我认为 要走的路是生成一个具有正确出现次数的数组 对于每个值,然后进行随机洗牌(保留频率) 并将洗牌数组的连续元素作为您的随机序列。

    【讨论】:

      【解决方案5】:

      好的,我的回答听起来像是一个 hack - 但简短或编写您自己的分布 - 也许您可以映射一个统一分布并利用提升(查看 http://www.boost.org/doc/libs/1_44_0/doc/html/boost_random/reference.html#boost_random.reference.distributions

      所以按照你的例子:

      • 1 -> 1
      • 2,3 ->2
      • 4,5,6 ->3
      • 7,8,9,10 ->4 (等等...)

      然后生成 1 到 10 之间的随机数并返回映射的元素。 然后使用 boost 的 uniform_int 分布来获取一个数字,然后将其映射。

      这里是一个生成数字的例子;然后你需要映射结果:

      #include <iostream>
      #include <boost/random.hpp>
      #include <time.h>
      using namespace std;
      using namespace boost;
      
      int main ( )  {
      
          uniform_int<> distribution(0, 10) ;
          mt19937 engine; 
          engine.seed(time(NULL));   
          variate_generator<mt19937, uniform_int<> > myrandom (engine, distribution);
      
          cout << myrandom() << endl;
      
      }
      

      【讨论】:

      • 标签是 C,而不是 C++。我不认为 boost 会有用。
      猜你喜欢
      • 2013-11-21
      • 2011-05-01
      • 2010-12-04
      • 2012-09-13
      • 1970-01-01
      • 2020-03-14
      • 2014-07-16
      • 2022-07-06
      • 1970-01-01
      相关资源
      最近更新 更多