phparrayssortingarray-splice

Reposition element to the start of its relative sequence/group in a flat, indexed array


Say I have an array such as:

[
    ['action' => 'Created', 'timestamp' => '2023-10-30 20:51:57.284602'],
    ['action' => 'Updated', 'timestamp' => '2023-10-30 20:51:57.284603'],
    ['action' => 'Started', 'timestamp' => '2023-10-30 20:51:57.284604'],
    ['action' => 'Bid placed', 'timestamp' => '2023-10-30 20:51:57.284605'],
    ['action' => 'Max bid placed', 'timestamp' => '2023-10-30 20:51:57.284606'],
    ['action' => 'Bid placed', 'timestamp' => '2023-10-30 20:51:57.284607'],
    ['action' => 'Max bid placed', 'timestamp' => '2023-10-30 20:51:57.284608'],
    // and so on...
    ['action' => 'Ended', 'timestamp' => '2023-10-30 20:51:57.284609'],
],

I need to swap the order of any "bid placed" items that are together with "max bid placed" items. It is possible that there might not be any "max bid placed" items. There could also be many of these entries in the array.

e.g.

[
    ['action' => 'Created', 'timestamp' => '2023-10-30 20:51:57.284602'],
    ['action' => 'Updated', 'timestamp' => '2023-10-30 20:51:57.284603'],
    ['action' => 'Started', 'timestamp' => '2023-10-30 20:51:57.284604'],
    ['action' => 'Max bid placed', 'timestamp' => '2023-10-30 20:51:57.284606'],
    ['action' => 'Bid placed', 'timestamp' => '2023-10-30 20:51:57.284605'],
    ['action' => 'Max bid placed', 'timestamp' => '2023-10-30 20:51:57.284608'],
    ['action' => 'Bid placed', 'timestamp' => '2023-10-30 20:51:57.284607'],
    // and so on...
    ['action' => 'Ended', 'timestamp' => '2023-10-30 20:51:57.284609'],
],

I've tried a few things already but always seem to end up the following order:

[
    ['action' => 'Created'],
    ['action' => 'Updated'],
    ['action' => 'Started'],
    ['action' => 'Max bid placed'],
    ['action' => 'Max bid placed'],
    ['action' => 'Bid placed'],
    ['action' => 'Bid placed'],
    // and so on...
    ['action' => 'Ended'],
],

Is this actually possible?


Solution

  • As you iterate, cache the starting point of a given group.

    If a started group encounters a non-group entry before encountering a Max bid placed row, then abort the group.

    When a group is started and suitably concluded with a Max bid placed row, then remove the Max bid placed row and re-inject it before the first row in the group. The array will be automatically re-indexed by the array_splice() function call. The subsequent row indexes will be unaffected by each manipulation because there will be no increase/decrease of the array size prior to the manipulation.

    Code: (Demo)

    $start = null;
    foreach ($array as $i => $row) {
        if ($row['action'] === 'Bid placed') {
            $start ??= $i;  // only store the first $i in the group
            continue;
        }
        if ($start !== null && $row['action'] === 'Max bid placed') {
            array_splice($array, $start, 0, array_splice($array, $i, 1)); // prune and reinsert row
        }
        $start = null;
    }
    var_export($array);
    

    I initially considered, but abandoned the following because it required too much sorting and memory for the relatively simple procedure of conditionally moving single rows in an array.

    //                          DON'T USE THIS CODE, IT IS NOT CORRECT.
    $grouper = [];
    $actions = [];
    $timestamps = [];
    $i = 0;
    foreach ($array as $row) {
        $grouper[] = $i;
        $actions[] = $row['action'];
        $timestamps[] = $row['timestamp'];
        $i += $row['action'] !== 'Bid placed';
    }
    array_multisort($grouper, $actions, SORT_DESC, $timestamps, $array);
    var_export($array);