Search code examples
phparraysdistribution

Distribute total amount to a flat array of "container" elements with predefined limits


I'm trying to split items evenly through groups with a max on each group.

I have a function which receives a $groups array which declares the maximum capacity of each "group" and an $items integer which declares the total value which can be distributed across the elements of $groups.

This is my function so far:

public static function splitItems($groups, $items) {
    $n = count($groups);
    $res = array_fill(0, $n, 0);

    while ($items >= $groups[0]) {
        for ($i = $n - 1; $i >= 0; $i--) {
            if ($items >= $groups[$i]) {
                $res[$i]++;
                $items -= $groups[$i];
            }
        }
    }

    if ($items > 0) {
        $res[0]++;
    }
    return $res;
}

My function isn't properly taking into account the VALUE of array as max value. What I'm looking for are this results:

Input:

splitItems([14, 2], 10);
splitItems([6, 8, 2], 14);

Output:

Array
(
    [0] => 8
    [1] => 2
)
Array
(
    [0] => 6
    [1] => 6
    [2] => 2
)

Solution

  • This should work:

    function splitItems($groups, $items)
    {
        $n = count($groups);
        $res = array_fill(0, $n, 0);
        $groupsFilled = array_fill(0, $n, 0);;
    
        while ($items > 0 && count(array_keys($groupsFilled, 1)) < count($groupsFilled))
        {
            for ($i = $n-1; $i >= 0; $i--)
            {
                if ($res[$i] < $groups[$i])
                {
                    if ($items > 0) {
                        $res[$i]++;
                        $items--;
                    }
                }
                else $groupsFilled[$i] = 1;
            }
        }
    
        return $res;
    }
    
    print_r(splitItems([14,2],10));
    print_r(splitItems([6,8,2],14));
    print_r(splitItems([6,4,2],14)); //not enough space for all the items!
    

    Outputs:

    Array
    (
        [0] => 8
        [1] => 2
    )
    Array
    (
        [0] => 6
        [1] => 6
        [2] => 2
    )
    Array
    (
        [0] => 6
        [1] => 4
        [2] => 2
    )
    

    Key points:

    • The code loops until there are no more items left to distribute (in most cases, but see the last bullet point below).
    • As per your original logic it will put an item the last group first, and then work backwards to the first one. It distributes one item to each group, then goes round again until there's nothing left.
    • It checks to ensure the group is not full before putting an item into it.
    • If you specify less total space in the groups than there are items, it will just fill the groups until there is no space left in any of them - that's what the second condition in the while loop is doing. (What it doesn't do is tell you how many items couldn't be put into a group - but you could alter that if you wanted to.)

    Live demo: https://3v4l.org/R8BOg