使用C#以一定概率触发事件

时间:2012-04-30 10:34:48

标签: c# random simulation probability

我正在尝试模拟真实的按键事件。出于这个原因,我正在使用SendInput()方法,但为了获得更好的结果,我需要指定keyDOWN和KeyUP事件之间的延迟!下面这些数字显示DOWN和UP事件之间经过的时间(以毫秒为单位)(这些是真实/有效):

96 95 112 111 119 104 143 96 95 104 120 112 111 88 104 119 111 103 95 104 95 127 112 143 144 142 143 128 144 112 111 112 120 128 111 135 118 147 96 135 103 64 64 87 79 112 88 111 111 112 111 104 87 95

我们可以简化输出:

延迟64 - 88 ms - > 20%的时间

延迟89 - 135 ms - > 60%的时间

延迟136 - 150 ms - > 20%的时间

如何根据上面的概率触发事件?以下是我现在正在使用的代码:

        private void button2_Click(object sender, EventArgs e)
        {
            textBox2.Focus();
            Random r = new Random();
            int rez = r.Next(0, 5); // 0,1,2,3,4 - five numbers total

            if (rez == 0) // if 20% (1/5)
            {
                Random r2 = new Random();
                textBox2.AppendText(" " + rez + " " + r2.Next(64, 88) + Environment.NewLine);
// do stuff
            }
            else if (rez == 4)//if 20% (1/5)
            {
                Random r3 = new Random();
                textBox2.AppendText(" " + rez + " " + r3.Next(89, 135) + Environment.NewLine);
// do stuff

            }
            else // if 1 or 2 or 3 (3/5) -> 60%
            {
                Random r4 = new Random();
                textBox2.AppendText(" " + rez + " " + r4.Next(136, 150) + Environment.NewLine);
// do stuff

            }

        }

此代码存在严重问题。理论上,经过数百万次迭代后 - 结果图看起来与此类似:

comparison

如何处理这个问题?

编辑:解决方案是按照人们的建议使用分发。

这里是这种代码的java实现:

http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Random.html#nextGaussian%28%29

这是C#实现:

How to generate normally distributed random from an integer range?

虽然我建议稍微减少“偏差”的值。

这是有趣的msdn文章

http://blogs.msdn.com/b/ericlippert/archive/2012/02/21/generating-random-non-uniform-data-in-c.aspx

每个人都感谢您的帮助!

3 个答案:

答案 0 :(得分:2)

这是正确的想法,我只是认为你需要使用双打而不是整数,这样你就可以在0和1之间划分概率空间。这样你就可以获得更精细的粒度,如下所示:

  1. 通过将所有值除以最大值
  2. 来标准化实际值
  3. 将值划分为桶 - 桶越多,图表越接近连续案例
  4. 现在,存储桶越大,事件被提升的可能性就越大。因此,根据每个桶中的元素数量来划分区间[0,1]。因此,如果您有20个实际值,并且存储桶中有5个值,则占用间隔的四分之一。
  5. 在每次测试中,使用Random.NextDouble()和随机数落入的任意一个桶生成0-1之间的随机数,使用该参数引发事件。因此,对于您提供的数字,以下是5个桶存储桶的值:
  6. enter image description here

    这有点像代码示例,但希望这给出了正确的想法

答案 1 :(得分:1)

听起来你需要生成正态分布。内置的.NET类生成Uniform Distribution

使用Box-Muller transform使用内置Random类可以实现高斯或正态分布随机数。

你应该得到像这样的好概率曲线

Normal Distribution

(摘自http://en.wikipedia.org/wiki/Normal_distribution

要将正态分布的随机数转换为整数范围,Box-Muller变换可以再次帮助解决这个问题。请参阅此previous question and answer,其中描述了过程和数学证明的链接。

答案 2 :(得分:1)

一种可能的方法是将延迟建模为Exponential Distribution。指数分布模拟连续和独立地以恒定平均速率发生的事件之间的时间 - 对于您的问题,这听起来是公平的假设。

您可以通过取实际观察到的延迟的平均值的倒数来估计参数lambda,并使用this approach模拟分布,即

delay = -Math.Log(random.NextDouble())/ lambda

然而,看看你的样本,数据看起来太“集中”在平均值附近是一个纯指数,因此模拟这种方式会导致延迟的正确均值,但过于分散以匹配你的样本。

解决这个问题的一种方法是将流程建模为移位指数;基本上,该过程被移动一个值,该值表示该值可以采用的最小值,而不是指数的0。在代码中,将移位作为样本中的最小观察值,这可能如下所示:

var sample = new List<double>()
                  {
                     96,
                     95,
                     112,
                     111,
                     119,
                     104,
                     143,
                     96,
                     95,
                     104,
                     120,
                     112
                  };

var min = sample.Min();
sample = sample.Select(it => it - min).ToList();

var lambda = 1d / sample.Average();

var random = new Random();
var result = new List<double>();
for (var i = 0; i < 100; i++)
{
   var simulated = min - Math.Log(random.NextDouble()) / lambda;
   result.Add(simulated);
   Console.WriteLine(simulated);
}

一个简单的替代方案,实质上类似于Aidan的方法,是重新采样:从原始样本中挑选随机元素,结果将具有完全所需的分布:

var sample = new List<double>()
                  {
                     96,
                     95,
                     112,
                     111,
                     119,
                     104,
                     143,
                     96,
                     95,
                     104,
                     120,
                     112
                  };

var random = new Random();
var size = sample.Count();
for (var i = 0; i < 100; i++)
{
   Console.WriteLine(sample[random.Next(0, size)]);
}