我有一个函数(来自未解决的previous question),它创建了一个包含n个值的数组。数组的总和等于$ max。
function randomDistinctPartition($n, $max) {
$partition= array();
for ($i = 1; $i < $n; $i++) {
$maxSingleNumber = $max - $n;
$partition[] = $number = rand(1, $maxSingleNumber);
$max -= $number;
}
$partition[] = $max;
return $partition;
}
例如:如果我设置$ n = 4且$ max = 30.那么我应该得到以下内容。
array(5, 7, 10, 8);
但是,此功能不考虑重复项和0。我想要 - 并且一直在努力实现 - 是生成一个具有唯一数字的数组,这些数字加起来我的预定变量 $ max 。 无重复数字和否0和/或负整数。
答案 0 :(得分:13)
好的,这个问题实际上围绕着线性序列。最小值为1时,请考虑以下序列:
f(n) = 1 + 2 + ... + n - 1 + n
这样一个序列的总和等于:
f(n) = n * (n + 1) / 2
因此,对于n = 4,例如,总和为10.这意味着如果您选择4个不同的数字,则没有零且没有负数的最小总数为10.现在反过来:如果您有总数10个和4个数字然后只有一个组合(1,2,3,4)。
首先,您需要检查您的总数是否至少与此下限一样高。如果它少了就没有组合。如果它相等,那么恰好有一种组合。如果它更高则会变得更复杂。
现在假设您的约束共有12个,包含4个数字。我们已经确定f(4)= 10.但是如果第一个(最低)数是2?
怎么办?2 + 3 + 4 + 5 = 14
所以第一个数字不能高于1.你知道你的第一个数字。现在,您生成一个包含3个数字的序列,总共11个(12 - 1)。
1 + 2 + 3 = 6
2 + 3 + 4 = 9
3 + 4 + 5 = 12
第二个数字必须是2,因为它不能是一个。它不能是3,因为从3开始的三个数字的最小总和是12,我们必须加到11。
现在我们发现两个数字加起来为9(12 - 1 - 2),其中3是最低的。
3 + 4 = 7
4 + 5 = 9
第三个数字可以是3或4.找到第三个数字后,最后一个是固定的。两种可能的组合是:
1, 2, 3, 6
1, 2, 4, 5
您可以将其转换为通用算法。考虑这种递归实现:
$all = all_sequences(14, 4);
echo "\nAll sequences:\n\n";
foreach ($all as $arr) {
echo implode(', ', $arr) . "\n";
}
function all_sequences($total, $num, $start = 1) {
if ($num == 1) {
return array($total);
}
$max = lowest_maximum($start, $num);
$limit = (int)(($total - $max) / $num) + $start;
$ret = array();
if ($num == 2) {
for ($i = $start; $i <= $limit; $i++) {
$ret[] = array($i, $total - $i);
}
} else {
for ($i = $start; $i <= $limit; $i++) {
$sub = all_sequences($total - $i, $num - 1, $i + 1);
foreach ($sub as $arr) {
array_unshift($arr, $i);
$ret[] = $arr;
}
}
}
return $ret;
}
function lowest_maximum($start, $num) {
return sum_linear($num) + ($start - 1) * $num;
}
function sum_linear($num) {
return ($num + 1) * $num / 2;
}
输出:
All sequences:
1, 2, 3, 8
1, 2, 4, 7
1, 2, 5, 6
1, 3, 4, 6
2, 3, 4, 5
这样做的一个实现是获取所有序列并随机选择一个序列。这样做的好处是可以同等地加权所有可能的组合,这些组合可能对你正在做的事情有用或者没有用。
对于大总数或大量元素,这将变得难以处理,在这种情况下,可以修改上述算法以返回从$start
到$limit
的范围内的随机元素,而不是每个值。
答案 1 :(得分:2)
我会在'三角形'公式下使用'区域...就像cletus(!?) 我真的要开始更多地关注事物......
无论如何,我认为这个解决方案现在非常优雅,它在所有元素之间应用所需的最小间距,均匀地,均匀地缩放间隙(分布)以保持原始总和并且非递归地完成工作(除了排序) ):
给定一个长度为n
的随机数的a()数组生成排序索引s()
并处理已排序的间隔a(s(0)) - a(s(1)),a(s(1)) - a(s(2))等
增加每个间隔 期望的最小分离尺寸,例如1 (这必然会扭曲他们的 '随机性')
将每个间隔减少一个因子 计算恢复系列总和 如果没有添加它是什么 间距。
如果我们在每个系列中加1,我们将系列和增加1 * len
1添加到每个系列间隔增加总和: len *(len + 1)/ 2 //(?pascal的三角形)
草案代码:
$series($length); //the input sequence
$seriesum=sum($series); //its sum
$minsepa=1; //minimum separation
$sorti=sort_index_of($series) //sorted index - php haz function?
$sepsum=$minsepa*($length*($length+1))/2;
//sum of extra separation
$unsepfactor100=($seriesum*100)/($seriesum+sepsum);
//scale factor for original separation to maintain size
//(*100~ for integer arithmetic)
$px=series($sorti(0)); //for loop needs the value of prev serie
for($x=1 ; $x < length; $x++)
{ $tx=$series($sorti($x)); //val of serie to
$series($sorti($x))= ($minsepa*$x) //adjust relative to prev
+ $px
+ (($tx-$px)*$unsepfactor100)/100;
$px=$tx; //store for next iteration
}
生成排序索引后,将索引的顺序混洗为重复值,以避免以冲突序列的顺序运行。 (或者只是在订单永不重要的情况下将最终输出洗牌)
欺骗指数:
for($x=1; $x<$len; $x++)
{ if ($series($srt($x))==$series($srt($x-1)))
{ if( random(0,1) )
{ $sw= $srt($x);
$srt($x)= $srt($x-1);
$srt($x-1)= $sw;
} } }
一种对“随机序列”的最小干扰可以通过以最小要求分开欺骗来实现,而不是将它们移动到最小 - 问题所寻求的“随机”量。
此处的代码将每个元素用最小分离区分开,无论是否重复,这应该是公平的,但也可能过度。可以修改代码以仅通过查看系列(sorti(n0:n1..len))来分离欺骗,并为每个欺骗计算sepsum为+ = minsep *(len-n)。然后调整循环只需要在应用调整之前再次测试欺骗。