Get `n` random values between 2 numbers having ave

2020-04-20 08:42发布

问题:

I want to get n random numbers(e.g n=16)(whole numbers) between 1 to 5(including both) so that average is x.

x can be any value between (1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5).

I am using PHP.

e.g. Suppose I have average x= 3.

Then required 16 whole numbers between 1 to 5(including both). like (1,5,3,3,3,3,2,4,2,4,1,5,1,5,3,3)

Update:

if x=3.5 means average of 16 numbers should be between 3.5 to 4.
and if x=4 means average of 16 numbers should be between 4 to 4.5
and if x=5 means all numbers are 5

回答1:

This answer allows any value for the target average (regardless of whether n is odd or even) and avoids the use of recursion to optimize performance.

The Function

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;
  }
}


Example Output:

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


回答2:

Edit: I have re-written this to avoid having to call the function recursively.

<?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());


回答3:

I would implement it like this:

  1. Choose n random numbers
  2. Calculate average
  3. Randomly choose one of the n random numbers
  4. Add or subtract 1 from the number, depending on whether the current average is above or below x
  5. Repeat from step 2 until the current average is x (or close enough)


回答4:

If I got it right, I would suggest you to have the average, than generate a number below the average and then to add a number with the same distance from average to the other side. For example, average 4 has the maximum border distance of 1 from nearest limit 5, so you should generate between 3, 4, 5. If 3 is generated - put 5 next. If 5, then 3. If 4 - put 4 next. And so on 8 times.

The best way to solve it is to put it this way:

average = sum of all numbers / amount of numbers, therefor, average * amount = sum, as Michael said. Now, if your sum is not integer - you will have no solution for this one.

It means that no matter, which method to use - mine, or Michael's. The difference is Michael's method doubles randomness with larger execution time.