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();
}
}
}
我有几个问题:
- 你能检查一下注释行是否正确或需要解释吗?
- 为什么
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;
}
}
您的测试例程似乎已正确翻译。
我已将 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();
}
}
}
我有几个问题:
- 你能检查一下注释行是否正确或需要解释吗?
- 为什么
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;
}
}
您的测试例程似乎已正确翻译。