C# 中具有概率的随机数

Random number with Probabilities in C#

我已将 this Java program 转换为 C# 程序。

using System;
using System.Collections.Generic;

namespace RandomNumberWith_Distribution__Test
{
    public class DistributedRandomNumberGenerator
    {

        private Dictionary<Int32, Double> distribution;
        private double distSum;

        public DistributedRandomNumberGenerator()
        {
            distribution = new Dictionary<Int32, Double>();
        }

        public void addNumber(int val, double dist)
        {
            distribution.Add(val, dist);// are these two
            distSum += dist;            // lines correctly translated?
        }

        public int getDistributedRandomNumber()
        {
            double rand = new Random().NextDouble();//generate a double random number
            double ratio = 1.0f / distSum;//why is ratio needed?
            double tempDist = 0;

            foreach (Int32 i in distribution.Keys)
            {
                tempDist += distribution[i];

                if (rand / ratio <= tempDist)//what does "rand/ratio" signify? What does this comparison achieve?
                {
                    return i;
                }
            }
            return 0;
        }
    }

    public class MainClass
    {
        public static void Main(String[] args)
        {
            DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
            drng.addNumber(1, 0.2d);
            drng.addNumber(2, 0.3d);
            drng.addNumber(3, 0.5d);

            //================= 
            // start simulation
            int testCount = 1000000;
            Dictionary<Int32, Double> test = new Dictionary<Int32, Double>();

            for (int i = 0; i < testCount; i++)
            {
                int random = drng.getDistributedRandomNumber(); 

                if (test.ContainsKey(random)) 
                {
                    double prob = test[random];   // are these
                    prob = prob + 1.0 / testCount;// three lines
                    test[random] = prob;          // correctly translated?
                }
                else
                {
                    test.Add(random, 1.0 / testCount);// is this line correctly translated?
                }
            }

            foreach (var item in test.Keys)
            {
                Console.WriteLine($"{item}, {test[item]}");
            }

            Console.ReadLine();
        }
    }
}

我有几个问题:

  1. 你能检查一下注释行是否正确或需要解释吗?
  2. 为什么 getDistributedRandomNumber() 在进行进一步计算之前不检查分布总和 1

方法

public void addNumber(int val, double dist)

翻译不正确,因为您缺少以下几行:

if (this.distribution.get(value) != null) {
    distSum -= this.distribution.get(value);
}

当您调用以下代码(基于您的示例代码)时,这些行应该涵盖这种情况:

DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.2d);
drng.addNumber(1, 0.5d);

因此使用相同的第一个参数调用方法 addNumber 两次,缺少的代码部分会检查第一个参数是否已经存在于字典中,如果存在,它将从中删除 "old" 值插入新值的字典。

您的方法应如下所示:

public void addNumber(int val, double dist)
{
    if (distribution.TryGetValue(val, out var oldDist)) //get the old "dist" value, based on the "val"
    {
        distribution.Remove(val); //remove the old entry
        distSum -= oldDist; //substract "distSum" with the old "dist" value
    }

    distribution.Add(val, dist); //add the "val" with the current "dist" value to the dictionary
    distSum += dist; //add the current "dist" value to "distSum"
}

现在开始你的第二种方法

public int getDistributedRandomNumber()

与其在每次调用此方法时都调用初始化 Random 的新实例,不如将其初始化一次,因此请更改行

double rand = new Random().NextDouble();

至此

double rand = _random.NextDouble();

并在 class 声明中的方法之外初始化字段 _random 像这样

public class DistributedRandomNumberGenerator
{
    private Dictionary<Int32, Double> distribution;
    private double distSum;
    private Random _random = new Random();        


    ... rest of your code
}

如果在循环中调用,这将防止 new Random().NextDouble() 一遍又一遍地生成相同的数字。 您可以在此处阅读有关此问题的信息:Random number generator only generating one random number

如我所言,c# 中的字段以前缀下划线命名。您应该考虑将 distribution 重命名为 _distribution,同样适用于 distSum.


下一个:

double ratio = 1.0f / distSum;//why is ratio needed?

比率是必需的,因为该方法会尽力使用您提供的信息来完成它的工作,假设您只调用它:

DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.2d);
int random = drng.getDistributedRandomNumber(); 

通过这些行,您告诉 class 您希望在 20% 个案例中获得编号 1,但是其他 80% 呢?

这就是比率变量的用武之地,它根据您给出的概率总和计算出一个可比较的值。 例如

double ratio = 1.0f / distSum;

与最新示例一样,drng.addNumber(1, 0.2d); distSum 将是 0.2,这转化为概率 20%

double ratio = 1.0f / 0.2;

比率为 5.0,比率为 5 的概率为 20%,因为 100% / 5 = 20%

现在让我们看看代码在比率为 5

时的反应
double tempDist = 0;
foreach (Int32 i in distribution.Keys)
{
    tempDist += distribution[i];

    if (rand / ratio <= tempDist)
    {
        return i;
    }
}

rand 将是任何给定时间大于或等于 0.0 且小于 1.0 的值,这就是 NextDouble 的工作原理,所以让我们假设以下 0.254557522132321 作为 rand.

现在让我们一步步看看会发生什么

double tempDist = 0; //initialize with 0 
foreach (Int32 i in distribution.Keys) //step through the added probabilities
{
    tempDist += distribution[i]; //get the probabilities and add it to a temporary probability sum

    //as a reminder
    //rand = 0.254557522132321
    //ratio = 5
    //rand / ratio = 0,0509115044264642
    //tempDist = 0,2
    // if will result in true
    if (rand / ratio <= tempDist)
    {
        return i;
    }
}

如果我们不应用比率,if 将是假的,但那是错误的,因为我们的字典中只有一个值,所以无论 rand 值是什么if 语句应该 return 为真,这就是 rand / ratio.

的本质

到"fix"根据我们添加的概率总和随机生成的数字。 rand / ratio 仅在您未提供完美总结为 1 = 100%.

的概率时才有用

例如。如果你的例子是这个

DistributedRandomNumberGenerator drng = new DistributedRandomNumberGenerator();
drng.addNumber(1, 0.2d);
drng.addNumber(2, 0.3d);
drng.addNumber(3, 0.5d);

您可以看到提供的概率等于 1 => 0.2 + 0.3 + 0.5,在本例中是行

if (rand / ratio <= tempDist)

看起来像这样

if (rand / 1 <= tempDist)

除以 1 永远不会改变值 rand / 1 = rand,因此此除法的唯一用例是您没有提供完美 100% 概率的情况,可以是更多或更少。

作为旁注,我建议将您的代码更改为此

//call the dictionary distributions (notice the plural)
//dont use .Keys
//var distribution will be a KeyValuePair
foreach (var distribution in distributions)
{
    //access the .Value member of the KeyValuePair
    tempDist += distribution.Value;

    if (rand / ratio <= tempDist)
    {
        return i;
    }
}

您的测试例程似乎已正确翻译。