skip to Main Content

i’m trying to solve this problem:

I’ve 3 percentage (%) stored in three callers: (Caller1, Caller2 and Caller3) like this:

$callers = [
    'Caller1' => 50,
    'Caller2' => 30,
    'Caller3' => 20
];

and I’ve ten numbers to call, i want to assign the numbers to each caller based on their percentage.

Example:

if numbers are (1, 2, 3 ,4 ,5 ,6 ,7 ,8,9,10), then the calls should be split like the bellow sequence based on their percentage (Note: like the below sequence):

  • Caller1: 1,4,7,9,10
  • Caller2: 2,5,8
  • Caller3: 3,6

my code like this:

class MyClassName
{
    // Each caller with their call percentage
    private $callers = [
        'caller1' => 50,
        'caller2' => 30,
        'caller3' => 20
    ];

    public $caller1 = array();
    public $caller2 = array();
    public $caller3 = array();

    public function splitCalls(array $numbers)
    {
        //count numbers
        //get spesific number of each percentage
        //loop on numbers and push numbers into 3 arrays based on percentage
        $count = count($numbers);
        $callerOneNumbers = floor(($this->callers['caller1'] / 100) * $count);
        $callerTwoNumbers = floor(($this->callers['caller2'] / 100) * $count);
        $callerThreeNumbers = floor(($this->callers['caller3'] / 100) * $count);  

        for ($i = 0; $i <= $count; $i++) {
            if (count($this->caller1) < $callerOneNumbers) {
                array_push($this->caller1, $numbers[$i]);
                $i++;
            }

            if (count($this->caller2) < $callerTwoNumbers) {
                array_push($this->caller2, $numbers[$i]);
                $i++;
            }

            if (count($this->caller3) < $callerThreeNumbers) {
                array_push($this->caller3, $numbers[$i]);
                $i++;
            }
        }
    }
}

$obj = new MyClassName;
$split = $obj->splitCalls([1, 2, 3, 4, 5, 6, 7, 8,9,10]);
var_dump($obj->caller1);
var_dump($obj->caller2);
var_dump($obj->caller3);

my result like this:

array (size=3)
  0 => int 1
  1 => int 5
  2 => int 9
C:wamp64wwwtest.php:102:
array (size=3)
  0 => int 2
  1 => int 6
  2 => int 10
C:wamp64wwwtest.php:103:
array (size=2)
  0 => int 3
  1 => int 7

but i want like this:

array (size=3)
  0 => int 1
  1 => int 4
  2 => int 7
  3 => int 9
  4 => int 10


array (size=2)
  0 => int 2
  1 => int 5
  2 => int 8


array (size=1)
  0 => int 3
  1 => int 6

any help please

3

Answers


  1. You can be flexible with any amount by calculating a distribution array. Then you know how many items need to go in there and can sequential iterate through it.

    $distribute = function(array $callers, array $data): array {
        $distribution = array_values(array_map(fn($percentage) => (int) $percentage/100 * count($data), $callers));
        $results = [];
        $index = 0;
        $length = count($callers);
        foreach ($data as $value) {
            $added = false;
            while (!$added) {
                $row = $index % $length;
                if ($distribution[$row] > 0) {
                    $results[$row][] = $value;
                    $distribution[$row]--;
                    $added = true;
                }
                $index++;
            }
        }
    
        return array_combine(array_keys($callers), $results);
    };
    
    echo json_encode($distribute(['Caller1' => 50, 'Caller2' => 30, 'Caller3' => 20], range(1,10)));
    

    Output

    JSON encoded for nicer display.

    {"Caller1":[1,4,7,9,10],"Caller2":[2,5,8],"Caller3":[3,6]}
    
    Login or Signup to reply.
  2. The main problem with your code is that at ever loop, you have $i++ (in the for() loop). This skips 1 array entry per loop (4 and 8 in this example). So a simple solution to your problem would be to change the loop to…

    for ($i = 0; $i < $count;) {
    

    as highlighted in the comments, this can cause a problem if the split doesn’t give a nice round split. Changing to using round instead of floor will solve this, although the split can seem unbalanced.

    e.g with 50, 30, 20 split, 2 items will give [[1], [2], []]. If this is OK, then follow this method.

    Login or Signup to reply.
  3. I support @MarkusZeller’s coding intention to have a "distribution" array. Here is my implementation of the same technique. My snippet converts percentage values into integers. Then while looping the integers will be decremented while also consuming the "numbers" array.

    The following approach does not break when the total percentage is something other than 100%. It will endeavor to distribute all numbers and to do so fairly.

    Code: (Demo)

    function splitCallers($callers, $numbers) {
        $count = count($numbers);
        $limits = array_map(
            fn($v) => (int) $count * $v / 100,
            $callers
        );
    
        $result = [];
        while ($limits && $numbers) {
            foreach ($limits as $caller => &$limit) {
                if (!$limit) {
                    unset($limits[$caller]);
                    continue;
                }
                if (!$numbers) {
                    break;
                }
                --$limit;
                $result[$caller][] = array_shift($numbers);
            }
        }
        return $result;
    }
    var_export(splitCallers($callers, range(1, 10)));
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search