I have an associative array (actually a Laravel collection) with this structure:
$array = [
["firstname" => "John", "lastname" => "", "email" => "", "uri" => ""],
["firstname" => "", "lastname" => "Smith", "email" => "", "uri" => ""],
["firstname" => "", "lastname" => "", "email" => "[email protected]", "uri" => ""]
];
How can I combine/merge/reduce the whole multidimensional array, so that I end up with a flattened structure containing all columns and prioritizing non-empty values?
Something like this:
[
['firstname' => 'John', 'lastname' => 'Smith', 'email' => '[email protected]', 'uri' => '']
]
I'll demonstrate a few alternative techniques with different benefits that rely on the fact that all columns are represented in all rows. I don't see any benefit to generating a 2-dimensional output array, so all of my snippets will produce the same flat, associative array.
No matter which style you prefer, at least two loops will be required -- one to iterate rows and one to iterate the elements in each row.
Standard nested loops: (Demo)
$result = array_shift($array); // remove the first row and push it into the result array
foreach ($array as $row) {
foreach ($row as $key => $value) {
if ($value !== '') {
$result[$key] = $value; // only overwrite where a non-empty value exists
}
}
}
var_export($result);
Semi-function-based iteration: (Demo)
$result = array_shift($array); // remove the first row and push it into the result array
foreach ($array as $row) {
$result = array_merge($result, array_filter($row, 'strlen')); // merge only non-empty elements into the result
}
var_export($result);
*Note, just in case your actual project has more than your posted four fields AND one of those fields may hold a zero value that you don't want to filter out, use strlen
as a parameter in array_filter()
. Otherwise, array_filter()
will greedily kill off an null, false, zeroish, and zero-width values.
Fully-function-based with array_reduce()
: (Demo)
var_export(
array_reduce(
$array,
function($carry, $row) {
$carry = array_merge($carry, array_filter($row, 'strlen'));
return $carry;
},
array_shift($array)
)
);
Fully-function-based with array_merge_recursive()
: (Demo)
var_export(
array_map('max', array_merge_recursive(...$array))
);
This one is most brief, but is - admittedly - taking advantage of the fact that max()
will return the desired strings from each column. This approach may not be suitable for all scenarios as a general-use technique.
Effectively, this is unpacking $array
into $row[0], $row[1], $row[2]
(using ...
) then transposing the data into subarrays of column data (maintaining the first level keys using array_merge_recursive()
), then reducing each subarray to its "highest" value (using max()
on each subarray).
Sahil's answer is not very robust/trustworthy because it manually corrects the missing uri
key's value after the process is complete. This adjustment is made purely because the missing value is "known" to the programmer. Programmatically speaking, this is lazy, prone to failure, and should not be used by SO readers.