我最终修改了 Severin 的解决方案以更好地满足我的需求,所以我想我在这里分享它,以防有人遇到同样的问题。我做了什么:
- 将
Categorical 替换为基于Random 类的自己的代码,因为Categorical 给了我奇怪的结果。
- 更改了概率的计算方式。
- 添加了更多统计信息。
要更改的关键参数是ratio:
- 最小值为 1.0,这使得它的行为就像一个随机数生成器
- 值越高,越类似于洗牌算法,因此数字保证在不久的将来出现并且不重复。顺序仍然无法预测。
比率 1.0 的结果:
这就像伪随机数生成。
3, 5, 3, 3, 3, 3, 0, 3, 3, 5, 5, 5, 2, 1, 3, 5, 3, 3, 2, 3, 1, 0, 4, 1, 5, 1, 3, 5, 1, 5, -
Number of occurences:
2
5
2
12
1
8
Max occurences in a row:
1
1
1
4
1
3
Max length where this number did not occur:
14
13
12
6
22
8
比率 5.0 的结果
我的最爱。很好的分布,偶尔重复,没有那么长的间隔,有些数字没有发生。
4, 1, 5, 3, 2, 5, 0, 0, 1, 3, 2, 4, 2, 1, 5, 0, 4, 3, 1, 4, 0, 2, 4, 3, 5, 5, 2, 4, 0, 1, -
Number of occurences:
5
5
5
4
6
5
Max occurences in a row:
2
1
1
1
1
2
Max length where this number did not occur:
7
10
8
7
10
9
比率 1000.0 的结果
非常均匀的分布,但仍然带有一些随机性。
4, 5, 2, 0, 3, 1, 4, 0, 1, 5, 2, 3, 4, 3, 0, 2, 5, 1, 4, 2, 5, 1, 3, 0, 2, 4, 5, 0, 3, 1, -
Number of occurences:
5
5
5
5
5
5
Max occurences in a row:
1
1
1
1
1
1
Max length where this number did not occur:
8
8
7
8
6
7
代码:
using System;
using System.Linq;
namespace EqualizedSampling
{
class Program
{
static Random rnd = new Random(DateTime.Now.Millisecond);
/// <summary>
/// Returns a random int number from [0 .. numNumbers-1] range using probabilities.
/// Probabilities have to add up to 1.
/// </summary>
static int Sample(int numNumbers, double[] probabilities)
{
// probabilities have to add up to 1
double r = rnd.NextDouble();
double sum = 0.0;
for (int i = 0; i < numNumbers; i++)
{
sum = sum + probabilities[i];
if (sum > r)
return i;
}
return numNumbers - 1;
}
static void Main(string[] args)
{
const int numNumbers = 6;
const int numSamples = 30;
// low ratio makes everything behave more random
// min is 1.0 which makes things behave like a random number generator.
// higher ratio makes number selection more "natural"
double ratio = 5.0;
double[] probabilities = new double[numNumbers];
int[] counter = new int[numNumbers]; // how many times number occured
int[] maxRepeat = new int[numNumbers]; // how many times in a row this number (max)
int[] maxDistance = new int[numNumbers]; // how many samples happened without this number (max)
int[] lastOccurence = new int[numNumbers]; // last time this number happened
// init
for (int i = 0; i < numNumbers; i++)
{
counter[i] = 0;
maxRepeat[i] = 0;
probabilities[i] = 1.0 / numNumbers;
lastOccurence[i] = -1;
}
int prev = -1;
int numRepeats = 1;
for (int k = 0; k < numSamples; k++)
{
// sample next number
//var cat = new Categorical(probabilities);
//var q = cat.Sample();
var q = Sample(numNumbers, probabilities);
Console.Write($"{q}, ");
// affect probability of the selected number
probabilities[q] /= ratio;
// rescale all probabilities so they add up to 1
double sumProbabilities = 0;
probabilities.ToList().ForEach(d => sumProbabilities += d);
for (int i = 0; i < numNumbers; i++)
probabilities[i] /= sumProbabilities;
// gather statistics
counter[q] += 1;
numRepeats = q == prev ? numRepeats + 1 : 1;
maxRepeat[q] = Math.Max(maxRepeat[q], numRepeats);
lastOccurence[q] = k;
for (int i = 0; i < numNumbers; i++)
maxDistance[i] = Math.Max(maxDistance[i], k - lastOccurence[i]);
prev = q;
}
Console.WriteLine("-\n");
Console.WriteLine("Number of occurences:");
counter.ToList().ForEach(Console.WriteLine);
Console.WriteLine();
Console.WriteLine("Max occurences in a row:");
maxRepeat.ToList().ForEach(Console.WriteLine);
Console.WriteLine();
Console.WriteLine("Max length where this number did not occur:");
maxDistance.ToList().ForEach(Console.WriteLine);
Console.ReadLine();
}
}
}