Search code examples
phparrayssummappinggrouping

Group 2d array data by one column, populate subarrays with defaults, sum values from another column using a mapping array


I am trying to group data from a 2d array while leveraging a lookup array to ensure that default values exist in each group.

My lookup/mapping array associates language ids with language names.

$langs = [
    5 => "english",
    10 => "french",
    12 => "german"
    ...
];

The other array holds the data which ahould be grouped by date value.

$posts = [
    [
        "date" => "13-07-2022",
        "lang_id" => 5,
        "amount" => 90,
    ],
    [
        "date" => "13-07-2022",
        "lang_id" => 10,
        "amount" => 34,
    ],
    [
        "date" => "14-07-2022",
        "lang_id" => 5,
        "amount" => 7,
    ],
    ...
];

Where the $posts have the post in that language per day. When there weren't any posts, there won't be any entry in the array.

I want to restructure the data to be grouped by date and each subarray should contain the date and the accumulated, associative language amounts:

$result = [
    [
        "date" => "13-07-2022",
        "english" => 90,
        "french" => 34,
        "german" => 0
    ],
    [
        "date" => "14-07-2022",
        "english" => 6,
        "french" => 0,
        "german" => 0
    ],
    ...
];

This is, when there no posts from the "master" list of languages, I will set that language to 0.

I tried to iterate the $posts and within it the langs array in order to fill with zeros each new processed item, but I'm failing to fulfil it. What I tried is:

$d = $post[0]['date'];
$result = [];

foreach ($posts as $post) {
    if ($d != $post['date']) {
        $d = $post['date'];
        $result[] = $proc;
        $proc = [];
        $proc['date'] = $d;
    }

    foreach ($langs as $id => $name) {
        if ($id == $post['lang_id']) {
            $proc[$name] = $post['amount'];
        } elseif (!isset($proc[$name])) {
            $proc[$name] = 0;
        }
    }
}

$result[] = $proc;

My point is if there would be a better way to achieve this with no nested loop or more efficient way


Solution

  • If you loop through the posts array and for each item, first check if there is already a result for this date. If there isn't - it adds the values from langs in using array_fill_keys() with a value of 0, and then adds the date in (you could make the date first by using array_merge()).

    Then it always adds in the various keys...

    $results = [];

    foreach ($posts as $post) {
        if (array_key_exists($post['date'], $results) == false) {
            $results[$post['date']] = array_fill_keys($langs, 0);
            $results[$post['date']]['date'] = $post['date'];
        }
        $results[$post['date']][$langs[$post['lang_id']]] = $post['amount'];
    }
    
    var_dump(array_values($results));
    

    which gives...

    array(2) {
      [0] =>
      array(4) {
        'english' =>
        int(90)
        'french' =>
        int(34)
        'german' =>
        int(0)
        'date' =>
        string(10) "13-07-2022"
      }
      [1] =>
      array(4) {
        'english' =>
        int(7)
        'french' =>
        int(0)
        'german' =>
        int(0)
        'date' =>
        string(10) "14-07-2022"
      }
    }
    

    For putting the date first...

    $results[$post['date']] = array_merge(['date' => $post['date']], array_fill_keys($langs, 0));