用新的随机生成的值替换数组中的重复值

时间:2010-05-08 15:15:46

标签: php algorithm combinatorics data-partitioning

我有一个函数(来自未解决的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和/或负整数

2 个答案:

答案 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 (这必然会扭曲他们的 '随机性')

  2. 将每个间隔减少一个因子  计算恢复系列总和  如果没有添加它是什么  间距。

  3. 如果我们在每个系列中加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 
      }
    
    • 所有间隔都减少了一个 常数(非随机翘曲因子)
    • 分离可以设置为其他值
    • 实施需要谨慎 调整(我通常测试&''校准')
      以适应舍入错误。 大概将所有内容扩展到~15 然后回来了。如果做得好,间隔期应该存在。

    生成排序索引后,将索引的顺序混洗为重复值,以避免以冲突序列的顺序运行。 (或者只是在订单永不重要的情况下将最终输出洗牌)

    欺骗指数:

    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)。然后调整循环只需要在应用调整之前再次测试欺骗。