在2个具有平均值“x”的数字之间得到“n”个随机值

时间:2012-03-30 13:07:57

标签: php algorithm random range average

我希望得到n个随机数(例如n = 16)(整数)介于1到5之间(包括两者),以便平均值为x。

x可以是(1,1.5,2,2.5,3,3.5,4,4.5,5)之间的任何值。

我正在使用PHP。

e.g。 假设我有平均x = 3.

然后需要16个1到5之间的整数(包括两者)。 像(1,5,3,3,3,3,2,4,2,4,1,5,1,5,3,3)

更新

如果x = 3.5意味着16个数字的平均值应该在3.5到4之间 如果x = 4则意味着16个数字的平均值应在4到4.5之间 如果x = 5则表示所有数字都是5

5 个答案:

答案 0 :(得分:4)

此答案允许目标平均值的任何值(无论n是奇数还是偶数),并避免使用递归来优化性能。

功能

function getRandomNumbersWithAverage($target_average, $n, $min=1, $max=5)
{

  if($min>$max) list($min, $max) = array($max, $min);
  if($target_average<$min || $target_average>$max) return false;
  else if($target_average==$min) return array_fill(0, $n, $min);
  else if($target_average==$max) return array_fill(0, $n, $max);

  if($n<1) return false;
  else if($n==1) return array($target_average);
  else
  {
    $numbers = array();
    for($i=0;$i<$n;$i++)
    {
      $sum = array_sum($numbers);
      $average = $i ? $sum/($i+1) : ($min+$max)/2;
      $contrived_number = $target_average*($i+1) - $sum;
      // Last one must be contrived
      if($i==$n-1) $new_number = ceil($contrived_number); // Round up
      else
      {
        // The tolerance gets smaller with iteration
        $tolerance = ($max-$min)*(1-($i/($n-1)));
        $temp_min = ($contrived_number-$tolerance);
        if($temp_min<$min) $temp_min = $min;
        $temp_max = ($contrived_number+$tolerance);
        if($temp_max>$max) $temp_max = $max;
        $new_number = mt_rand($temp_min, $temp_max);
      }
      if($new_number==0) $new_number = 0; // Handle -0
      $numbers[] = $new_number;
    }
    // Since the numbers get more contrived towards the end, it might be nice to shuffle
    shuffle($numbers);
    return $numbers;
  }
}


示例输出:

getRandomNumbersWithAverage(1, 12)

produced the numbers: (1,1,1,1,1,1,1,1,1,1,1,1) having an average of: 1


getRandomNumbersWithAverage(1.1, 13)

produced the numbers: (1,1,1,1,1,1,1,4,1,1,1,0,1) having an average of: 1.1538461538462


getRandomNumbersWithAverage(2.7, 14)

produced the numbers: (3,3,2,5,1,2,4,3,3,2,3,3,3,1) having an average of: 2.7142857142857


getRandomNumbersWithAverage(2.7, 15)

produced the numbers: (3,3,4,3,4,2,1,1,3,2,4,1,5,1,4) having an average of: 2.7333333333333


getRandomNumbersWithAverage(3.5, 16)

produced the numbers: (5,5,4,3,1,5,5,1,2,5,3,3,4,4,4,2) having an average of: 3.5


getRandomNumbersWithAverage(3.5, 17)

produced the numbers: (5,2,3,5,4,1,2,3,5,4,5,4,2,3,5,3,4) having an average of: 3.5294117647059


getRandomNumbersWithAverage(4, 18)

produced the numbers: (3,5,5,3,5,5,3,4,4,4,5,2,5,1,5,4,5,4) having an average of: 4


getRandomNumbersWithAverage(4.9, 19)

produced the numbers: (5,5,5,5,7,5,5,5,5,6,5,3,5,5,3,5,5,5,5) having an average of: 4.9473684210526


getRandomNumbersWithAverage(5, 20)

produced the numbers: (5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5) having an average of: 5


getRandomNumbersWithAverage(0.5, 10)

does not produce numbers


getRandomNumbersWithAverage(0, 9)

does not produce numbers


getRandomNumbersWithAverage(-1, 8)

does not produce numbers


getRandomNumbersWithAverage(5.5, 7)

does not produce numbers


getRandomNumbersWithAverage(6, 6)

does not produce numbers


getRandomNumbersWithAverage(6, 5, 1, 7)

produced the numbers: (7,7,2,7,7) having an average of: 6


getRandomNumbersWithAverage(6, 5, 1, 6)

produced the numbers: (6,6,6,6,6) having an average of: 6


getRandomNumbersWithAverage(3, 1)

produced the numbers: (3) having an average of: 3

答案 1 :(得分:1)

编辑:我重写了这个,以避免以递归方式调用该函数。

<?php

  /**
   * Get an array of random numbers between the given range with a given average value
   *
   * @param integer $min
   * @param integer $max
   * @param integer $count
   * @param integer|float $average
   * @return boolean|array
   */
  function getRandomNumbers($min = 1, $max = 5, $count = 16, $average = 3)
  {

    // Return FALSE if the range and/or the count are not all integers
    if (!is_int($min) || !is_int($max) || !is_int($count))
    {
      return FALSE;
    }

    // Round the average if the target total would be impossible
    if (!is_int($count * $average))
    {
      $average = round($average);
    }

    // Get the target total
    $total = $count * $average;

    // Return FALSE is the result is impossible
    if ($min > $max || $min * $count > $total || $max * $count < $total)
    {
      return FALSE;
    }

    // Get the specified number of random integers
    for ($i = 0; $i < $count; ++$i)
    {

      // Get a random number within the given range
      $rand = mt_rand($min, $max);

      // As a default do not continue
      $cont = FALSE;

      // Check to see if the random number is acceptable and if not change it until it is
      while (!$cont)
      {

        // If the number is too high then decrease it by one
        if (($total - $rand) - (($count - 1 - $i) * $min) < 0)
        {
          --$rand;
        }

        // Otherwise if the number is too low then increase it by one
        elseif (($total - $rand) - (($count - 1 - $i) * $max) > 0)
        {
          ++$rand;
        }

        // Otherwise we can continue
        else
        {
          $cont = TRUE;
        }

      }

      // Store the number and minus it from the total
      $total -= $result[] = $rand;

    }

    // Return the result
    return $result;

  }

  // Output an array of random numbers
  print_r(getRandomNumbers());

答案 2 :(得分:1)

我会像这样实现它:

  1. 选择n个随机数
  2. 计算平均值
  3. 随机选择n个随机数之一
  4. 从数字中加1或减1,具体取决于当前平均值是高于还是低于x
  5. 从步骤2开始重复,直到当前平均值为x(或足够接近)

答案 3 :(得分:1)

我为此编写了JS实现:

function getRandom(min, max) {
  return Math.random() * (max - min) + min;
}
function getArr(size, avg, min, max) {
  let arr = [];
  let tmax = max;
  let tmin = min;
  while (arr.length < size) {
    const variable1 = +getRandom(min, tmax).toFixed(1);
    let variable2 = +(avg * 2 - variable1).toFixed(1);
    if (variable2 < min) {
      tmax = max - (min - variable2);
      variable2 = min;
    } else if (variable2 > max) {
      tmin = min + (variable2 - max);
      variable2 = max;
    } else {
      tmax = max;
      tmin = min;
    }
    arr = arr.concat([variable1, variable2]);
  }
  let sumErr = arr.reduce((a, b) => a + b, 0) - avg * size;
  if (sumErr > 0) {
    arr = arr.map((x) => {
      if (x > min && sumErr > 0.001) {
        let maxReduce = x - min;
        if (maxReduce > sumErr) {
          const toReturn = +(x - sumErr).toFixed(1);
          sumErr = 0;
          return toReturn;
        } else {
          sumErr -= maxReduce;
          return min;
        }
      }
      return x;
    });
  } else {
    arr = arr.map((x) => {
      if (x < max && sumErr < -0.001) {
        let maxAdd = max - x;
        if (maxAdd > Math.abs(sumErr)) {
          const toReturn = +(x + Math.abs(sumErr)).toFixed(1);
          sumErr = 0;
          return toReturn;
        } else {
          sumErr += maxAdd;
          return max;
        }
      }
      return x;
    });
  }

  return arr.sort(() => Math.random() - 0.5);
}

const output = getArr(40, 2.01, 0.5, 2.5)
const outputAvg = output.reduce((a, b) => a + b, 0) / 40
console.log(`givenAvg: ${2.01} outputAvg: ${outputAvg}`)
console.log(output)

答案 4 :(得分:0)

如果我做对了,我会建议你得到平均值,而不是产生一个低于平均值的数字,然后添加一个从平均到另一边的距离相同的数字。例如,平均值4的最大边界距离为1,距离最近限制为5,因此您应该生成3,4,5之间。如果生成3,则接下来放5。如果是5,那么3.如果4 - 接下来放4。等8次。

解决问题的最佳方法是这样说:

平均值=所有数字/数量之和,因此, 迈克尔说,平均值*金额=总和。现在,如果你的总和不是整数 - 你将无法解决这个问题。

这意味着无论使用哪种方法 - 我的,还是迈克尔的。不同的是迈克尔的方法使随机性加倍,执行时间更长。