因为我今天因为流感被困在家里 :( 我决定尝试为你解决这个问题。基本上你要求的是某种插值。我使用了最简单的(线性)和这些是我的结果和代码。代码有点乱,我可能会在未来几天修复它..
<?php
// this function interpolates $a to $b over $steps steps, starting from key $k
// this can be cleaned up significantly
function interpolate($a, $b, $steps, $k) {
@$per_step = abs($a - $b)/$steps; // suppress warnings in case of division by zero
if ($a > $b)
$decreasing = true;
else
$decreasing = false;
$final = array();
for ($i = 1; $i <= $steps-1; ++$i) {
if ($decreasing)
$final[$i+$k] = $a-=$per_step; // linear interpolation
else
$final[$i+$k] = $a+=$per_step; // linear interpolation
}
return $final;
}
// this function combines probability arrays after the interpolation occurs
// this may happen multiple times, think about 1, 3, 5. interpolation would have to occur
// from 1 -> 2 -> 3, and from 3 -> 4 -> 5.
function interpolateProbabilities ($nodes) {
$pNodes = array();
$pNodes = $nodes;
$keys = array_keys($nodes);
for ($i = 0; $i < count($keys); $i++) {
if ($keys[$i+1] - $keys[$i] != 1) {
$pNodes += interpolate($nodes[$keys[$i]], $nodes[$keys[$i+1]], $keys[$i+1] - $keys[$i], $keys[$i]);
}
}
ksort($pNodes);
return $pNodes;
}
// this generates a weighed random value and is pretty much copy-pasted from:
// http://w-shadow.com/blog/2008/12/10/fast-weighted-random-choice-in-php/
// it's robust and re-writing it would be somewhat pointless
function generateWeighedRandomValue($nodes) {
$weights = array_values($nodes);
$values = array_keys($nodes);
$count = count($values);
$i = 0;
$n = 0;
$num = mt_rand(0, array_sum($weights));
while($i < $count) {
$n += $weights[$i];
if($n >= $num) {
break;
}
$i++;
}
return $values[$i];
}
// two test cases
$nodes = array( 1 => 12, 5 => 22, 9 => 31, 10 => 35); // test 1
$nodes = array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10); // test 2
$export = array();
// run it 1000 times
for ($i = 0; $i < 1000; ++$i) {
$export[generateWeighedRandomValue(interpolateProbabilities($nodes))]++;
}
// for copy-pasting into excel to test out distribution
print_r($export);
?>
我认为,结果正是您想要的。
在这种情况下:
$nodes = array( 1 => 12, 5 => 22, 9 => 31, 10 => 35); // test 1
我得到了以下(最终)数组:
Array
(
[5] => 92
[7] => 94
[10] => 162
[8] => 140
[3] => 71
[6] => 114
[2] => 75
[4] => 69
[9] => 131
[1] => 52
)
也就是说,1 应该发生 12% 的时间,5 22%,9 31% 和 10 35% 的时间。让我们绘制它:
看起来很有希望,但让我们尝试一些更疯狂的东西......
$nodes = array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10); // test 2
在这种情况下,3 应该出现 50% 的时间,然后急剧下降到 6。让我们看看发生了什么!这是数组(回想起来,我应该对这些数组进行排序):
Array
(
[4] => 163
[7] => 64
[2] => 180
[10] => 47
[1] => 115
[5] => 81
[3] => 227
[8] => 57
[6] => 6
[9] => 60
)
让我们看看图片:
它看起来有效:)
我希望我能够解决您的问题(或至少为您指明正确的方向)。请注意,我的代码目前有许多规定。也就是说,您提供的初始节点的概率必须为 100%,否则您可能会遇到一些不稳定的行为。
另外,代码有点乱,但概念相对简单。其他一些很酷的东西是尝试而不是使用线性插值,而是使用其他类型,这会给你带来更有趣的结果!
算法
为避免混淆,我将准确展示该算法的工作原理。
我给 PHP 一个 $node 数组,其形式为 integer => frequency in percentage,最终看起来类似于 array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10),从上方看是 test 2。
Test 2 基本上表示您希望将 5 个控制节点放置在1, 3, 6, 7, and 10,频率分别为22%, 50%, 2%, 16%, and 10%。首先,我需要准确地查看需要进行插值的何处。例如,我不需要在6 和7 之间做,但我做需要在1 和3 之间做(我们需要插值2 ) 和 7 和 10(我们需要插入 8 和 9)。
1 -> 3 之间的插值有(3 - 1) - 1 = 1 步长,应插入原始数组中的key[2]。 1 -> 3 插值的值 (%) 是 abs($a - $b) / $steps,它转换为 1 的 % 的绝对值减去 2 的 % 除以 steps + 1,其中我们的例子恰好等于14。我们需要看看函数是增加还是减少(你好微积分)。如果函数正在增加,我们会继续添加步 % 到新的插值数组,直到我们填满所有空点(如果函数正在减少,我们减去步 % value。因为我们只需要填一个位置,我们返回2 => 36 (22 + 14 = 36)。
我们组合数组,结果是(1 => 22, 2 => 36, 3 => 50, 6 => 2, 7 => 16, 10 => 10)。程序插入了2,这是一个我们没有明确声明的百分比值。
在7 -> 10的情况下,有2个步骤,步骤百分比是2,来自(16-10) / (3 + 1) = 2。函数是递减的,所以我们需要反复减去2。最终的插值数组是(8 => 14, 9 => 12)。我们结合所有的数组,瞧。
下图显示了绿色(初始值)和红色(插值)。您可能必须“查看图像”才能清楚地看到整个事情。您会注意到我使用了±,因为算法需要确定我们应该在一段时间内增加还是减少。
这段代码可能应该以更多的 OOP 范式编写。我经常使用数组键(例如,我需要传递$k,因此一旦我从interpolate($a, $b, $steps, $k) 返回它们,组合数组就更容易了,因为它们自动拥有正确的键。这只是一个PHP 特质,回想起来,我可能应该从更易读的 OOP 方法开始。
这是我最后一次编辑,我保证 :) 因为我喜欢玩 Excel,所以这显示了在插入数字后百分比如何正常化。看到这一点很重要,特别是考虑到在您的第一张照片中,您所展示的内容在数学上有些不可能。
Test 1
Test 2
您会注意到百分比显着下降以适应插值。您在现实中的第二个图表看起来更像这样:
在这张图表中,我称重了1 = > 1, 5 => 98, 10 => 1,您可以看到阻尼效果的极端情况。毕竟,根据定义,百分比加起来必须是 100!重要的是要意识到阻尼效果与极端之间的步数成正比。