user537137
user537137

Reputation: 614

Group and sum data from a 2d array then map totals to another 2d array

I need to sum a column of values in one array grouped by another column and map those results to another array based on a specific column value.

Sample Input:

$milestones = [
    [
        'id' => '1578453e53090b',
        'milestone' => 'Wireframe',
        'assigned_to' => 9
    ],
    [
        'id' => '1578453e530975',
        'milestone' => 'Development'
    ],
    [
        'id' => '1578453e530943',
        'milestone' => 'Design',
        'assigned_to' => 2
    ]
];

$tasks = [
    [
        'id' => '15786dc59333f2',
        'status' => 'Open',
        'progress' => 5,
        'milestone' => '1578453e53090b'
    ],
    [
        'id' => '15786dc669d451',
        'status' => 'Open',
        'progress' => 10,
        'milestone' => '1578453e53090b'
    ],
    [
        'id' => '15786dc7ccbea3',
        'status' => 'Open',
        'progress' => 20,
        'milestone' => '1578453e530943'
    ]
];

I need to get the sum of the progress values in the $tasks array grouped by milestone then store/append those totals to the $milestones array.

Desired Output:

[
    [
        'id' => '1578453e53090b',
        'milestone' => 'Wireframe',
        'assigned_to' => 9,
        'total' => 15
    ],
    [
        'id' => '1578453e530975',
        'milestone' => 'Development'
    ],
    [
        'id' => '1578453e530943',
        'milestone' => 'Design',
        'assigned_to' => 2,
        'total' => 20
    ]
]

Upvotes: 0

Views: 62

Answers (3)

mickmackusa
mickmackusa

Reputation: 47874

Using a nested loop approach is unnecessarily expensive -- it is simply doing too many unnecessary cycles while checking for matches in the inner loop.

Instead, perform one loop over the $milestones array and declare a unique reference to each row. Then perform one loop over the $tasks array to add each new encounter's progress value to its mapped/referenced row.

Code: (Demo)

foreach ($milestones as &$row) {
    $ref[$row['id']] = &$row;
}

foreach ($tasks as ['milestone' => $m, 'progress' => $p]) {
    $ref[$m]['total'] = ($ref[$m]['total'] ?? 0) + $p;
}
var_export($milestones);

This approach is assumed to be safe because ALL task rows have a milestone value which correlates to milestone row ids and ALL id values in the milestones array are unique.

Upvotes: 0

BeetleJuice
BeetleJuice

Reputation: 40886

Array mapping should do the trick:

//will add 'progress' key to each milestone
$tabulate_progress = function($milestone) use ($tasks) {
    $milestone['progress'] = 0; //start with 0 progress

    foreach($tasks as $task):
        //add to progress if we find a matching task
        if($task['milestone']===$milestone['id'])
            $milestone['progress']+=$task['progress'];
    endforeach;

    return $milestone;
};
$milestones = array_map($tabulate_progress,$milestones);

Here's a demo.

Upvotes: 2

Kevin
Kevin

Reputation: 41885

One of the ways to do this is to just use foreach. Use the first foreach for the milestones getting the ID that will match the second foreach for tasks in milestone.

After that, use a simple $total container to be used continually andatch them using a simple if.

foreach($milestones as &$m) {
                //     ^ add reference to make changes
    $total = 0; // initialize total holder
    foreach($tasks as $task) {
        if($task['milestone'] === $m['id']) { // if it matches
            $total += $task['progress']; // add
        }
    }

    $m['total'] = $total; // after its done, add another key pair being total and its value
}

Upvotes: 1

Related Questions