user2184072
user2184072

Reputation: 47

Fill an array with random numbers while obeying designated sum, count, and number boundaries

I have to fill an array with random numbers to satisfy a few conditions:

  1. The number of elements in the result array must match the designated number.
  2. The sum of the numbers in the result array must equal the designated number.
  3. Random numbers must be selected between designated lower and upper bounds.

For example:

Possible result:

array(23, 70, 37)

What to do now? How to split/divide my number?

I started with this (pseudo code):

i=0;
while(sum(number) > 0 and i < arraykeys){
    x = randomize(from, to)
    number = number - x
    myarray[i] = x
    i++
} 

Upvotes: 2

Views: 1064

Answers (2)

mickmackusa
mickmackusa

Reputation: 47804

I've written a custom function for portability and to meaningfully implement some guard conditions which throw exceptions when incoming parameters make the desired result impossible.

  1. Loop one less than $count times -- this is because the final element in the returned array is determined by the difference between the desired total and the sum of the randomly acquired values.
  2. Adjust the lower and upper bounds of the $scope array (if required) to ensure a successfully populated return array.
  3. Get a random integer, push it into the return array, then subtract it from the $total.
  4. When the looped processes are finished, push the remaining $total value as the final element in the return array.

Code: (Demo)

function getRandWithStipulations(int $total, int $count, array $scope): array
{
    if ($scope[0] > $scope[1]) {
        throw new Exception('Argument 3 (\$scope) is expected to contain a minimum integer then a maximum integer.');
    }
    if ($scope[0] * $count > $total) {
        throw new Exception('Arguments 2 (\$count) and 3 (\$scope) can only exceed argument 1 (\$total).');
    }
    if ($scope[1] * $count < $total) {
        throw new Exception('Arguments 2 (\$count) and 3 (\$scope) cannot reach argument 1 (\$total).');
    }
    $result = [];
    for ($x = 1; $x < $count; ++$x) { // count - 1 iterations
        $scope[0] = max($scope[0], $total - ($scope[1] * ($count - $x)));
        $scope[1] = min($scope[1], $total - ($scope[0] * ($count - $x)));
        $rand = rand(...$scope);
        $result[] = $rand;
        $total -= $rand;
    }
    $result[] = $total;
    return $result;
}
try {
    var_export(
        getRandWithStipulations(
            130,
            3,
            [23, 70]
        )
    );
} catch (Exception $e) {
    echo 'Caught exception: ',  $e->getMessage();
}

A few random results:

  • [60, 34, 36]
  • [23, 59, 48]
  • [67, 36, 27]
  • [47, 23, 60]

Upvotes: 0

Rizier123
Rizier123

Reputation: 59681

This should work for you:

Code explanation

  1. Workability

    The first thing we need to check is, if it is possible to build the goal out of numbers from the scope:

    if(checkWorkability($result, $goal, $amountOfElementsLeft, $scope))
    

    Means it just uses the highest values possible and looks if it is bigger than the goal.

  2. While loop

    In the while loop we need to check if we still have elements left which we can use:

    while($amountOfElementsLeft > 0)
    
  3. Scope adjustment

    Every iteration we need to check if we need to adjust the scope, so that at the end we will be able to build the goal.

    This means if the current sum of numbers + the highest possible number is bigger than the goal, we need to make the max value of the scope smaller.

    Also on the opposite side we need to make the min value of the scope bigger, when we can't reach our goal anymore.

Code

<?php


    $goal = 130;
    $amountOfElementsLeft = 3;
    $scope = [23, 70];

    $result= [];


    function adjustScope(array $result, $goal, $amountOfElementsLeft, $scope) {

        $newScope = $scope;

        if($amountOfElementsLeft == 1) {
            $leftOver = $goal - array_sum($result);
            return [$leftOver, $leftOver];
        }


        if((($goal - (array_sum($result) + $scope[1])) / ($amountOfElementsLeft - 1)) < $scope[0])
            $newScope[1] = (int) ($goal - array_sum($result)) / ($scope[0] * ($amountOfElementsLeft - 1));
        elseif(($adjustTop = $goal - array_sum($result)) < $scope[1])
            $newScope[1] = $adjustTop;

        if(($adjustBottom = $goal - (array_sum($result) + $scope[0] + (($amountOfElementsLeft - 1) * $scope[1]))) < $goal && $adjustBottom > 0)
            $newScope[0] = $scope[0] + $adjustBottom;

        return $newScope;

    }

    function checkWorkability(array $result, $goal, $amountOfElementsLeft, $scope) {
        if(array_sum($result) + $amountOfElementsLeft * $scope[1] >= $goal)
            return TRUE;
        return FALSE;
    }


    if(checkWorkability($result, $goal, $amountOfElementsLeft, $scope)) {
        while($amountOfElementsLeft > 0) {
            $scope = adjustScope($result, $goal, $amountOfElementsLeft, $scope);

            $result[] = rand($scope[0], $scope[1]);
            $amountOfElementsLeft--;

        }
    }

    print_r($result);
    echo array_sum($result);

?>

possible outputs:

Array
(
    [0] => 58
    [1] => 30
    [2] => 42
) -> 130
Array
(
    [0] => 35
    [1] => 54
    [2] => 41
) -> 130
Array
(
    [0] => 52
    [1] => 51
    [2] => 27
) -> 130

Upvotes: 2

Related Questions