0
votes

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

5
could you show some ideas you've also thought?? - Sujit Agarwal
The truly average can be only 3, if the numbers are truly random... - Michael Sazonov
Adding a constraint that the numbers require hitting a specified average value removes the randomness. For example in your example you could pick 8 random numbers from that group, then for each number find the other number where the average of the two is 3. That should work, but you don't have random values anymore for all 16 and you never will. - TheOx
I have updated question. - Somnath Muluk
So what have you actually tried so far? - TheOx

5 Answers

4
votes

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
1
votes

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());
1
votes

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)
1
votes

I have written the JS implementation for this:

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)
0
votes

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.