array_map, array_filter и array_reduce
В прошлом уроке гардеробщик переставлял вещи прямо на крючках. Но часто исходный массив нужен целым, а из него хочется получить другой: цены со скидкой, только чётные числа или общую сумму. Для этого в гардеробе работают ещё трое:
- портной перешивает каждую вещь и вешает копию на новую вешалку — это
array_map; - контролёр снимает лишние вещи, а остальные оставляет на их прежних крючках — это
array_filter; - кассир обходит все вещи и называет одно число (например, общую стоимость) — это
array_reduce.
Что именно делать, каждому из них говорит функция, которую вы передаёте. Чаще всего это стрелочная функция fn: одна строка с правилом.
array_map: новое значение для каждого
array_map вызывает вашу функцию для каждого значения массива и собирает ответы в новый массив того же размера. Функция стоит первым аргументом, массив — вторым:
<?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}Давайте разберём по шагам:
fn($p) => $p * 2говорит портному, что сделать с одним значением.array_mapвызывает эту функцию три раза — для100,250и40— и складывает ответы в новый массив$doubled.$pricesне изменился: портной шьёт копии, а старые вещи остаются на месте.- Ключи переезжают в новый массив: у
$stockбыли ключи'pear'и'kiwi', и у результата они те же.
array_filter: ключи остаются прежними
У array_filter порядок аргументов обратный: сначала массив, потом функция. Значение остаётся в результате, если функция вернула для него истинное значение, и выпадает, если ложное.
Главная ловушка здесь — ключи. Оставшиеся значения сохраняют свои исходные ключи: контролёр снимает лишние вещи, но номера крючков не переписывает. Поэтому после фильтра список становится массивом с дырами: его ключи идут не подряд.
Нажмите «Следующий шаг» и следите за ключами в $even и $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";
Список из четырёх чисел с ключами 0, 1, 2 и 3.
Правило, которое показывает виджет:
array_filterоставляет значения вместе с их ключами: у чётных чисел из[5, 8, 1, 6]ключи1и3;countдыр не видит: элементов два, хотя самый большой ключ —3;array_valuesберёт одни значения и нумерует их с0, и получается настоящий список.
Снаружи дыры заметны не сразу:
$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]Первая строка вывода, 8 6, выглядит как у обычного списка, а array_keys показывает настоящие ключи 1 3. json_encode пишет массив с дырами в фигурных скобках: для JSON это объект с ключами "1" и "3", а не список. Если такой ответ уходит в браузер, код на JavaScript получает объект вместо списка и может сломаться.
Этот вопрос часто задают на собеседовании: «Почему после array_filter в JSON появился объект?» Ответ: фильтр сохранил исходные ключи, а перенумеровать их нужно через array_values.
array_reduce: одно значение
array_reduce сворачивает весь массив в одно значение. Ей передают массив, функцию с двумя параметрами и начальное значение. Первый параметр функции — то, что накоплено к этому моменту, второй — очередной элемент:
$cart = [120, 80, 50];
$total = array_reduce($cart, fn($sum, $p) => $sum + $p, 0);
echo $total, "\n"; // 250
echo array_sum($cart), "\n"; // 250Давайте разберём, как считает кассир:
$sumначинается с третьего аргумента,0.- Первый вызов получает
$sum = 0и$p = 120и возвращает120. - Этот ответ становится
$sumво втором вызове:120 + 80даёт200. - Третий вызов складывает
200 + 50, иarray_reduceвозвращает250.
Здесь картинка с кассиром неточна. Кассир держит сумму в голове, а у вашей функции памяти нет: сумму ей каждый раз передаёт сама array_reduce в параметре $sum.
Для суммы есть готовая функция array_sum. А array_reduce берут, когда нужно своё правило: произведение, самое длинное слово, строка из всех имён.
У трёх функций разный порядок аргументов, и его часто путают:
array_map— сначала функция, потом массив;array_filter— сначала массив, потом функция;array_reduce— массив, функция и начальное значение.
Частая ошибка
После фильтра читают первый элемент по индексу 0:
$scores = [3, 9, 4, 10];
$high = array_filter($scores, fn($s) => $s > 5);
echo $high[0], "\n"; // ✗Warning: Undefined array key 0Число 3 с ключом 0 не прошло фильтр, поэтому ключа 0 в $high нет: там только ключи 1 и 3. PHP выдаёт предупреждение, и echo печатает пустую строку. Исправление — перенумеровать результат: $high = array_values(array_filter($scores, fn($s) => $s > 5));, и тогда $high[0] равно 9.
Главное
array_mapвозвращает новый массив того же размера с теми же ключами, а исходный не трогает.array_filterоставляет значения, для которых функция вернула истинное значение, вместе с их исходными ключами — в списке появляются дыры.array_valuesнумерует значения заново с0; без неёjson_encodeпишет массив с дырами как объект.array_reduceберёт начальное значение, передаёт результат каждого вызова в следующий и возвращает одно значение.