3

I need to merge the row data of my 2d array where rows share the same messageID value.

$myarray = [
    [
        'messageId' => '5ACE9D8841',
        'sender' => 'john@doe.com'
    ],
    [
        'messageId' => '7EE67D8170',
        'sender' => 'dan@doe.com'
    ],
    [
        'messageId' => '8095FD8836',
        'sender' => 'cat@doe.com'
    ],
    [
        'messageId' => '7EE67D8170',
        'dest' => 'mitch@doe.com',
        'status' => 'sent',
        'date' => '2021-02-01 11:06:55'
    ]
];

From the sample data, rows [1] and [3] are merged into [1] because they share the same [messageId]. Expected result:

Array
(
    [0] => Array
        (
            [messageId] => 5ACE9D8841
            [sender] => john@doe.com
        )

    [1] => Array
        (
            [messageId] => 7EE67D8170
            [dest] => mitch@doe.com
            [status] => sent
            [date] => 2021-02-01 11:06:55
            [sender] => dan@doe.com
        )

    [2] => Array
        (
            [messageId] => 8095FD8836
            [sender] => cat@doe.com
        )
)

I don't mind about the key index or the order.

EDIT : I've tried array_merge, array_merge_recursive and many others. Best result was obtained with

$result_arr = array();
foreach ($myarray as $sub_arr) {
    $result_arr = array_merge($result_arr, $sub_arr);
    $result_arr = array_unique($result_arr);
} 

It works but returns only the last iteration:

Array (
    [messageId] => 7EE67D8170
    [dest] => mitch@doe.com
    [status] => sent
    [date] => 2021-02-01 11:06:55
    [sender] => dan@doe.com
)
mickmackusa
  • 43,625
  • 12
  • 83
  • 136
dubwise
  • 33
  • 3
  • The core of your issue is that you're trying to merge on every iteration, unconditionally. You should only merge when a certain condition is met (the ids match). Otherwise, all you need to do is add the element as it is to the resulting array. You could use the message id as the key in your resulting array, so it'd be easy to identify if that message id already exists in the result and should be merged. – El_Vanja Feb 05 '21 at 16:17

2 Answers2

0

Try using array_reduce function with callback function:

$result = array_values(array_reduce($myarray, function($rows, $item){
    if (array_key_exists('messageId', $item) && is_scalar($item['messageId'])) {
        $rows = array_replace_recursive($rows ?? [], [$item['messageId'] => $item]);
    }
    return $rows;
}));

print_r($result);

fiddle

id'7238
  • 2,428
  • 1
  • 3
  • 11
  • hi, thank you very much for your help. On the fiddle example, I think it's almost good but sender ( 'sender' => 'dan@doe.com') is missing for messageId 7EE67D8170 – dubwise Feb 06 '21 at 11:39
  • I have updated the answer and fiddle. There should be a function `array_replace_recursive` instead of `array_merge`. – id'7238 Feb 06 '21 at 11:51
0

This can be done in a single loop by pushing reference variables into the result array. array_reduce() is a suitable native function when you want to produce an array that may have an output size/type that is different from your input array.

The static declaration ensures that the $ref array is not forgotten between each iteration. By pushing unique &$ref elements into the result array, you don't actually need to keep track of how to access the position of the row in the result array -- you only need to push data into the reference variable then the data will be perfectly delivered to the correct place in the result array.

Code: (Demo)

var_export(
    array_reduce(
        $myarray,
        function($result, $row) {
            static $ref;
            if (!isset($ref[$row['messageId']])) {
                $ref[$row['messageId']] = $row;
                $result[] = &$ref[$row['messageId']];
            } else {
                $ref[$row['messageId']] += $row;
            }
            return $result;
        }
    )
);

For those who prefer classic loops, a foreach() eliminates the static declaration and the return, but it does add a $result variable to the global scope.

Code: (Demo)

$result = [];
foreach ($myarray as $row) {
    if (!isset($ref[$row['messageId']])) {
        $ref[$row['messageId']] = $row;
        $result[] = &$ref[$row['messageId']];
    } else {
        $ref[$row['messageId']] += $row;
    }
}
var_export($result);

For those looking for the most readable/simple code and who do not mind the extra loop under the hood by calling array_values(), you can temporarily assign first level keys, then conditionally push/merge, then remove the temporary keys after looping.

Code: (Demo)

$result = [];
foreach ($myarray as $row) {
    if (!isset($result[$row['messageId']])) {
        $result[$row['messageId']] = $row;
    } else {
        $result[$row['messageId']] += $row;
    }
}
var_export(array_values($result));
mickmackusa
  • 43,625
  • 12
  • 83
  • 136