Метод
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
с одним и тем же первым аргументом, отсутствующая часть кода ищет, если первый аргумент уже присутствует в словаре, и если это так, он удалит «старое» значение из словаря для вставки нового значения.
Ваш метод должен выглядеть следующим образом:
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();
и инициализировать поле _random
вне метода внутри объявления класса, как это
public class DistributedRandomNumberGenerator
{
private Dictionary<Int32, Double> distribution;
private double distSum;
private Random _random = new Random();
... rest of your code
}
Это будет препятствовать тому, чтобы new Random().NextDouble()
производил одно и то же число снова и снова при вызове в al oop. Вы можете прочитать об этой проблеме здесь: Генератор случайных чисел, генерирующий только одно случайное число
Как я отмечаю, поля в 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();
С этими строками вы сказали классу, что вы хотите, чтобы число 1
в 20%
дел, но что о другом 80%
?
И вот здесь возникает переменная отношения, она вычисляет сопоставимое значение на основе суммы вероятностей, которые вы дали. например.
double ratio = 1.0f / distSum;
Как и в последнем примере, drng.addNumber(1, 0.2d);
distSum
будет 0.2
, что соответствует вероятности 20%
.
double ratio = 1.0f / 0.2;
. 5.0
, с вероятностью 20%
отношение равно 5, потому что 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 должен возвращать true, и это природный смысл rand / ratio
.
. Чтобы "исправить" случайно сгенерированное число на основе суммы вероятностей, которые мы добавили , 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;
}
}
Кажется, что ваша процедура тестирования переведена правильно.