PeerBr
PeerBr

Reputation: 715

Merge rows of two 2d arrays, group on a column, and append non-null values in another column within each group

I am refactoring a calendar app. The calendar has a user-defined grid (slots where bookings can occur), but needs to display "off-the-grid" bookings as well. Think of "regular" slots at 08:00, 09:00 and "irregular" slots when someone is booked at 08:39. For business reasons, I need to display these slots differently (CSS), otherwise they behave the same. I've searched the PHP Manual, but the built-in array functions don't do exactly what I need.

$officialGrid = [
    ['grid' => TRUE, 'time' => '08:00', 'content' => NULL],
    ['grid' => TRUE, 'time' => '09:00', 'content' => NULL],
];

$bookings = [
    ['grid' => NULL, 'time' => '08:00', 'content' => 'Paul Simon'],
    ['grid' => NULL, 'time' => '08:00', 'content' => 'Art Garfunkel'],
    ['grid' => NULL, 'time' => '08:39', 'content' => 'Homer J. Simpson'],
];

I could just append these arrays, but for performance reasons would like shorten the result to:

$timeSlotsToDisplay = [
    ['grid' => TRUE, 'time' => '08:00', 'content' => 'Paul Simon, Art Garfunkel'], //regular
    ['grid' => NULL, 'time' => '08:39', 'content' => 'Homer J. Simpson'], //irregular
    ['grid' => TRUE, 'time' => '09:00', 'content' => NULL], //vacant
];

I'm also flexible to change data types for the values (content might be an array). Is there any elegant solution for merging these arrays, other than start looping and comparing?

PS: Just to illustrate, in PostgreSQL terms, I need to SELECT DISTINCT ON (time) grid, time, string_agg(content) FROM myDB GROUP BY time ORDER BY time, grid; (please ignore possible keywords, not quoting due to formatting, also haven't tested the query).

Upvotes: 1

Views: 73

Answers (2)

mickmackusa
mickmackusa

Reputation: 47904

  1. Assign temporary first level keys to the $officialGrid, this will serve as the base data for the result array.
  2. Iterate the $bookings array.
  3. When a row's time is encountered for the first time, push it directly into the result array.
  4. If a row's time has been encountered before, extend the $content column value in the result array with a conditional comma&space then the new row's content value.
  5. After looping, sort by the temporary keys (time values).
  6. Reindex the result (remove the temporary keys) by calling array_values().

Code: (Demo)

$result = array_column($officialGrid, null, 'time');
foreach ($bookings as $row) {
    if (!isset($result[$row['time']])) {
        $result[$row['time']] = $row;
    } else {
        $result[$row['time']]['content'] = sprintf(
            '%s%s',
            (isset($result[$row['time']]['content']) ? "{$result[$row['time']]['content']}, " : ''),
            $row['content']
        );
    }
}
ksort($result);
var_export(array_values($result));

Upvotes: 0

Cheery
Cheery

Reputation: 16214

I see nothing wrong with the loops.. but I suggest another structure for the $officialGrid array

$officialGrid = array(
    '08:00' => array('grid' => TRUE, 'content' => NULL),
    '09:00' => array('grid' => TRUE, 'content' => NULL));

$bookings = array(
    array('grid' => NULL, 'time' => '08:00', 'content' => 'Paul Simon'),
    array('grid' => NULL, 'time' => '08:00', 'content' => 'Art Garfunkel'),
    array('grid' => NULL, 'time' => '08:39', 'content' => 'Homer J. Simpson'));

$timeSlotsToDisplay = $officialGrid;

array_map(function($el) use(&$timeSlotsToDisplay, $officialGrid) {
   $timeSlotsToDisplay[$el['time']] = array(
     'content' => 
       (isset($timeSlotsToDisplay[$el['time']]) ?
        trim($timeSlotsToDisplay[$el['time']]['content'], ',') . ',' : '') .
       $el['content'],
     'grid' => isset($officialGrid[$el['time']]) ? true : null
   );
}, $bookings);

ksort($timeSlotsToDisplay);
var_dump($timeSlotsToDisplay);

array_map can be replaced by a single foreach loop.

Upvotes: 1

Related Questions