array_map, array_filter and array_reduce
In the previous lesson the attendant rearranged items right on the hooks. But often you need the original array intact and want a different one made from it: discounted prices, only the even numbers or a grand total. Three more people work in the cloakroom for that:
- the tailor alters every item and hangs the copy on a new rack—that is
array_map; - the checker takes the extra items away and leaves the rest on their old hooks—that is
array_filter; - the cashier goes over all the items and names one number, such as the total cost—that is
array_reduce.
A function you pass in tells each of them what exactly to do. Most often it is an arrow function fn: one line holding the rule.
array_map: a new value for each one
array_map calls your function for every value of the array and collects the answers into a new array of the same size. The function is the first argument, the array the second:
<?php
$prices = [100, 250, 40];
$doubled = array_map(fn($p) => $p * 2, $prices);
echo implode(' ', $doubled), "\n"; // 200 500 80
echo implode(' ', $prices), "\n"; // 100 250 40
$stock = ['pear' => 7, 'kiwi' => 9];
echo json_encode(array_map(fn($n) => $n + 1, $stock)), "\n"; // {"pear":8,"kiwi":10}Let's go through it step by step:
fn($p) => $p * 2tells the tailor what to do with one value.array_mapcalls this function three times—for100,250and40—and puts the answers into a new array,$doubled.$priceshas not changed: the tailor makes copies, and the old items stay where they were.- The keys move into the new array:
$stockhad the keys'pear'and'kiwi', and the result has the same ones.
array_filter: the keys stay as they were
array_filter takes its arguments the other way round: the array first, then the function. A value stays in the result if the function returns a truthy value for it, and drops out if it returns a falsy value.
The main trap here is the keys. The values that remain keep their original keys: the checker takes the extra items away but does not renumber the hooks. So after a filter a list becomes an array with holes: its keys no longer run in a row.
Press "Next step" and watch the keys in $even and $list.
1<?php2 3$nums = [5, 8, 1, 6];4$isEven = fn($n) => $n % 2 === 0;5$even = array_filter($nums, $isEven);6$list = array_values($even);7echo count($even), ' ', count($list), "\n";8echo $even[1], ' ', $list[1], "\n";
A list of four numbers with the keys 0, 1, 2 and 3.
The rule the widget shows:
array_filterkeeps the values together with their keys: the even numbers of[5, 8, 1, 6]have the keys1and3;countdoes not see the holes: there are two elements, although the largest key is3;array_valuestakes the values alone and numbers them from0, which gives a real list.
From the outside the holes are not visible at once:
$nums = [5, 8, 1, 6];
$even = array_filter($nums, fn($n) => $n % 2 === 0);
echo implode(' ', $even), "\n"; // 8 6
echo implode(' ', array_keys($even)), "\n"; // 1 3
echo json_encode($even), "\n"; // {"1":8,"3":6}
echo json_encode(array_values($even)), "\n"; // [8,6]The first output line, 8 6, looks like an ordinary list, while array_keys shows the real keys, 1 3. json_encode writes the array with holes in curly braces: to JSON it is an object with the keys "1" and "3", not a list. If that reply goes to a browser, the JavaScript code gets an object instead of a list and can break.
Interviewers often ask this: "Why did an object show up in the JSON after array_filter?" The answer: the filter kept the original keys, and array_values is what renumbers them.
array_reduce: one value
array_reduce folds the whole array into one value. You pass it the array, a function of two parameters and an initial value. The function's first parameter is what has been accumulated so far, the second is the next element:
$cart = [120, 80, 50];
$total = array_reduce($cart, fn($sum, $p) => $sum + $p, 0);
echo $total, "\n"; // 250
echo array_sum($cart), "\n"; // 250Let's see how the cashier counts:
$sumstarts from the third argument,0.- The first call gets
$sum = 0and$p = 120and returns120. - That answer becomes
$sumin the second call:120 + 80gives200. - The third call adds
200 + 50, andarray_reducereturns250.
This is where the cashier picture falls short. A cashier keeps the total in their head, but your function has no memory: array_reduce itself passes it the total every time, in the $sum parameter.
For a sum there is a ready-made function, array_sum. array_reduce is what you reach for when you need your own rule: a product, the longest word, a string of all the names.
The three functions take their arguments in different orders, and people often mix them up:
array_map—the function first, then the array;array_filter—the array first, then the function;array_reduce—the array, the function and the initial value.
Common mistake
After a filter, the first element is read by index 0:
$scores = [3, 9, 4, 10];
$high = array_filter($scores, fn($s) => $s > 5);
echo $high[0], "\n"; // ✗Warning: Undefined array key 0The number 3 with the key 0 did not pass the filter, so $high has no key 0: it holds only the keys 1 and 3. PHP shows a warning, and echo prints an empty line. The fix is to renumber the result: $high = array_values(array_filter($scores, fn($s) => $s > 5));, and then $high[0] is 9.
Key points
array_mapreturns a new array of the same size with the same keys and leaves the original alone.array_filterkeeps the values for which the function returns a truthy value, together with their original keys—the list gets holes.array_valuesnumbers the values again from0; without itjson_encodewrites an array with holes as an object.array_reducepasses each call's result into the next one, starting from the initial value, and returns one value.