Перейти к содержимому
Теория · Функции для массивов · урок 3 из 8

array_map, array_filter и array_reduce

В прошлом уроке гардеробщик переставлял вещи прямо на крючках. Но часто исходный массив нужен целым, а из него хочется получить другой: цены со скидкой, только чётные числа или общую сумму. Для этого в гардеробе работают ещё трое:

  • портной перешивает каждую вещь и вешает копию на новую вешалку — это array_map;
  • контролёр снимает лишние вещи, а остальные оставляет на их прежних крючках — это array_filter;
  • кассир обходит все вещи и называет одно число (например, общую стоимость) — это array_reduce.

Что именно делать, каждому из них говорит функция, которую вы передаёте. Чаще всего это стрелочная функция fn: одна строка с правилом.

array_map: новое значение для каждого

array_map вызывает вашу функцию для каждого значения массива и собирает ответы в новый массив того же размера. Функция стоит первым аргументом, массив — вторым:

PHP
<?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}

Давайте разберём по шагам:

  1. fn($p) => $p * 2 говорит портному, что сделать с одним значением.
  2. array_map вызывает эту функцию три раза — для 100, 250 и 40 — и складывает ответы в новый массив $doubled.
  3. $prices не изменился: портной шьёт копии, а старые вещи остаются на месте.
  4. Ключи переезжают в новый массив: у $stock были ключи 'pear' и 'kiwi', и у результата они те же.

array_filter: ключи остаются прежними

У array_filter порядок аргументов обратный: сначала массив, потом функция. Значение остаётся в результате, если функция вернула для него истинное значение, и выпадает, если ложное.

Главная ловушка здесь — ключи. Оставшиеся значения сохраняют свои исходные ключи: контролёр снимает лишние вещи, но номера крючков не переписывает. Поэтому после фильтра список становится массивом с дырами: его ключи идут не подряд.

Нажмите «Следующий шаг» и следите за ключами в $even и $list.

ВживуюКлючи после array_filterшаг 1 из 10 · рамок: 1

Список из четырёх чисел с ключами 0, 1, 2 и 3.

Следите за ключами в $even и $list

Правило, которое показывает виджет:

  • array_filter оставляет значения вместе с их ключами: у чётных чисел из [5, 8, 1, 6] ключи 1 и 3;
  • count дыр не видит: элементов два, хотя самый большой ключ — 3;
  • array_values берёт одни значения и нумерует их с 0, и получается настоящий список.

Снаружи дыры заметны не сразу:

PHP
$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 сворачивает весь массив в одно значение. Ей передают массив, функцию с двумя параметрами и начальное значение. Первый параметр функции — то, что накоплено к этому моменту, второй — очередной элемент:

PHP
$cart = [120, 80, 50];
$total = array_reduce($cart, fn($sum, $p) => $sum + $p, 0);
echo $total, "\n"; // 250
echo array_sum($cart), "\n"; // 250

Давайте разберём, как считает кассир:

  1. $sum начинается с третьего аргумента, 0.
  2. Первый вызов получает $sum = 0 и $p = 120 и возвращает 120.
  3. Этот ответ становится $sum во втором вызове: 120 + 80 даёт 200.
  4. Третий вызов складывает 200 + 50, и array_reduce возвращает 250.

Здесь картинка с кассиром неточна. Кассир держит сумму в голове, а у вашей функции памяти нет: сумму ей каждый раз передаёт сама array_reduce в параметре $sum.

Для суммы есть готовая функция array_sum. А array_reduce берут, когда нужно своё правило: произведение, самое длинное слово, строка из всех имён.

У трёх функций разный порядок аргументов, и его часто путают:

  • array_map — сначала функция, потом массив;
  • array_filter — сначала массив, потом функция;
  • array_reduce — массив, функция и начальное значение.

Частая ошибка

После фильтра читают первый элемент по индексу 0:

PHP
$scores = [3, 9, 4, 10];
$high = array_filter($scores, fn($s) => $s > 5);
echo $high[0], "\n"; // ✗
TEXT
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 берёт начальное значение, передаёт результат каждого вызова в следующий и возвращает одно значение.