ruby 如何生成一个随机数的可能性,遵循修改(压扁)余弦曲线?

k5ifujac  于 2023-06-22  发布在  Ruby
关注(0)|答案(1)|浏览(89)

我试图生成一个数字,它遵循一个周期增加的余弦曲线。目标是生成0和1之间的数字,但能够提供幅度/因子和周期/间隔。
例如,给定以下:

10_000.times.map do
  num = randcos(factor: 5, intervals: 3)
  (num * 13).floor # Group in 12 groups for graphing visibility
end

然后根据频率绘制这些点,我想要一个看起来像这样的图表:

因为我们设置了intervals: 3,所以我们有3个“尖峰”,这些数字组更有可能。低点仍然会出现,但可能性约为1/5,因为我们设置了factor: 5
我一直在尝试使用Math.cos来实现这一点,但是当我试图将这些更高的数字Map到生成新集合的可能性时,我被卡住了。我可以让它显示一个“尖峰”只使用Math.sin/Math.cos,但我希望能够自定义有多少他们显示。
下面是我最近一次迭代的一个例子,尽管它只有一个降序图:

def randcos(factor:, intervals:)
  max_scale = intervals*Math::PI
  min_scale = -max_scale

  # Grab a random index and apply it to the squished cos
  x = min_scale + (2 * max_scale * rand) 
  num = (Math.cos(x)+1)/2 # Normalize 0..1 instead of -1..1

  # Trying to use the `num` from above to then multiply by the factor to give us 
  # more likely high numbers when the cos is nearer to 1
  rand * num * factor 
end
368yc8dk

368yc8dk1#

这听起来像一个有趣的问题,所以我试了一下。这段代码是用javascript写的,因为我比ruby更熟悉它,也因为它可以嵌入到S.O.中,但原理是一样的,你应该能够翻译它。
我分三部分做了这件事。

1.余弦公式

我们需要一个余弦函数,在最可能的数字和最不可能的数字之间具有给定的幅度。你把它命名为factor
我们还希望该函数在最小值处开始和结束,并达到最大值一定次数。您将其命名为intervals
这个函数看起来像:

其中a表示intervals,b表示factor。在这个例子中,我使用了因子5和间隔3来生成这个图。

这个函数允许我输入任何x来得到它的相对概率。
转换为JS代码的函数看起来像这样:

const func = (x) => (1 + ((factor - 1)*(1 - Math.cos(2*Math.PI*intervals*x))/2))/(factor);

2.概率权重预计算。

现在我们有了这个函数,我们可以确定任何数字出现的可能性有多大。Math.random()总是返回一个介于0和1之间的数字,我们希望将该值Map到可能值的范围。在您的示例中,这是0 - 12或13个可能的值。
我们上面定义的函数的域是[0,1],所以我们将其分成13个相等的步骤来计算每个数字的概率。

const stepLength = 1 / (definitionSteps-1);
//cosine function
const func = (x) => (1 + ((factor - 1)*(1 - Math.cos(2*Math.PI*intervals*x))/2))/(factor);
  
let weights = [];
for(let i = 0; i < definitionSteps; i++)
{
  weights.push(func(i * stepLength));
}

然后,我们将对每个权重进行累积求和,以使weights数组中的每个值等于其自身加上前面所有权重的总和。这为我们提供了每个指数的值范围,但比例是关闭的。为了解决这个问题,我们只需将数组中的每个值除以最大值(或最后一个值)。

3.Map值。

现在我们已经有了一个权重范围数组,我们可以简单地调用Math.random()来获得一个随机数,并将其Map到基于权重范围的可能值。
假设我有这些权重:[0.1, 0.3, 0.6, 1]
如果我调用Math.random()并得到0.4,它将Map到索引2,因为0.40.30.6之间。我们对预先计算的余弦权重做同样的事情。
n = 13,factor = 5,intervals = 3的实际权重数组如下所示:

[0.025, 0.096, 0.223, 0.318, 0.350, 0.398, 0.513, 0.628, 0.676, 0.707, 0.902, 0.929, 1]
  • 因此,0到0.025之间的任何数字都将Map到0。
  • 0.025和0.096之间的任何数字都将Map为1。
  • 等等

代码的最后一部分只是计算10,000个随机数,对它们进行排序,并将它们Map到各自的索引。我这样做是为了模拟10,000次运行的性能,但使用权重数组,您可以直接将[0,1]之间的任何随机数Map到其相应的权重。
最终的输出被打印到控制台,它看起来与您的问题中的模型图非常相似。在我的例子中,我得到:

[
  261,  //0
  783,  //1
  1387, //2
  810,  //3
  272,  //4
  801,  //5
  1375, //6
  833,  //7
  302,  //8
  800,  //9
  1309, //10
  795,  //11
  272   //12
]
const definitionSteps = 13;
const factor = 5;
const intervals = 3;
const count = 10000;

function calculateCosineWeights(factor, intervals)
{
  const stepLength = 1 / (definitionSteps-1);
  //cosine function
  const func = (x) => (1 + ((factor - 1)*(1 - Math.cos(2*Math.PI*intervals*x))/2))/(factor);
  
  let weights = [];
  for(let i = 0; i < definitionSteps; i++)
  {
    weights.push(func(i * stepLength));
  }
  
  weights.forEach((e, i) => {
    if(i > 0)
      weights[i] += weights[i - 1];
  });
  
  let max = weights[weights.length - 1];
  weights = weights.map(e => e / max);
  
  return weights;
}

let precalculatedWeights = calculateCosineWeights(factor, intervals);

function randcos(n)
{
  let vals = [];
  while(n > 0)
  {
    vals.push(Math.random());
    n--;
  }
  
  vals.sort((a, b) => a-b);
  
  let index = 0;
  let runningIndex = 0;
  let output = [];
  while(index < definitionSteps)
  {
    let x = 0;
    for(let i = runningIndex; i < vals.length && vals[i] < precalculatedWeights[index]; i++)
    {
      x++;
    }
    
    runningIndex += x;
    output.push(x);
    index++;
  }
  
  return output;
}

console.log(randcos(count));

相关问题