随机/不可预测的数字,分布更加均匀

时间:2018-12-19 14:06:49

标签: c# algorithm random

我用随机数生成器生成1-6的随机整数。我想更改世代以避免这种情况:

  • 第3次连续生成第3次
  • 2号不是最近30代中产生的

因此,总的来说,我希望在较短的时间内进行更多的数字分布。

我知道这样的数字不再是真正随机的,但是只要它们是不可预测的,就可以了。

这似乎是一个常见问题。有什么典型的解决方案,所以我不会重新发明轮子吗?

任何语言的代码都可以,但是C#优先。

更新:

我不确定问题为什么会被否决,也许我解释错了。

JohnColeman在评论中建议我需要像人类那样做的随机数生成-我认为这是一个很好的观点。

Fisher Yates Shuffle也是一个很好的建议。不是完美的,但对我来说是一种进步。

我可以考虑的另一种算法是为每个数字分配一个权重,并使选择该数字的概率与该权重成正比。每次选择一个数字,就可以减小其权重,并增加其他数字的权重。但这需要进行测试,性能可能很差(但这对我而言并不重要)。总的来说,我希望这个问题是已知的,并且已经有一些解决方案。

2 个答案:

答案 0 :(得分:3)

好吧,我认为我可以对您的案例应用曾经实施过的反加权(请参见How do I randomly equalize unequal values?)。

基本上,样本概率与其总体数量成反比。初始填充将是您的指导参数-如果它很高,则反之将很低,并且累加计数器的影响很小,因此它将非常接近均匀。如果初始人口很低(例如1),那么累积计数器会影响抽样。

要减少累积概率并返回原始概率时要考虑的第二个参数,否则低初始计数器的影响将随时间消散。

代码,使用Math .NET进行[0 ... 6)范围内的类别抽样,.NET Core 2.2,x64。

using System;
using System.Linq;
using MathNet.Numerics.Random;
using MathNet.Numerics.Distributions;

namespace EqualizedSampling
{
    class Program
    {
        static void Main(string[] args)
        {
            int increment         = 10; // how much inverse probabilities are updated per sample
            int guidanceParameter = 1000000; // Small one - consequtive sampling is more affected by outcome. Large one - closer to uniform sampling

            int[]    invprob = new int [6];
            double[] probabilities = new double [6];

            int[] counter = new int [] {0, 0, 0, 0, 0, 0};
            int[] repeat  = new int [] {0, 0, 0, 0, 0, 0};
            int prev = -1;
            for(int k = 0; k != 100000; ++k ) {
                if (k % 60 == 0 ) { // drop accumulation, important for low guidance
                    for(int i = 0; i != 6; ++i) {
                        invprob[i] = guidanceParameter;
                    }
                }
                for(int i = 0; i != 6; ++i) {
                    probabilities[i] = 1.0/(double)invprob[i];
                }
                var cat = new Categorical(probabilities);
                var q = cat.Sample();
                counter[q] += 1;
                invprob[q] += increment;
                if (q == prev)
                    repeat[q] += 1;
                prev = q;
            }
            counter.ToList().ForEach(Console.WriteLine);
            repeat.ToList().ForEach(Console.WriteLine);
        }
    }
}

我计算了重复的对以及数字的总数。引导参数较低时,连续对的外观会更均匀:

16670
16794
16713
16642
16599
16582
2431
2514
2489
2428
2367
2436

指导参数为1000000时,选择连续对的可能性更高

16675
16712
16651
16677
16663
16622
2745
2707
2694
2792
2682
2847

更新

我们可以添加另一个参数,每个样本增加一个。大的增量将使连续采样的可能性更大。代码已更新,输出

16659
16711
16618
16609
16750
16653
2184
2241
2285
2259
2425
2247

答案 1 :(得分:2)

我最终修改了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();
        }
    }
}