Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Агрегация (reduce) PHP: Функции

Последняя функция из нашей тройки — array_reduce используется для агрегации (название в других языках accumulate, fold или, по-русски, "свёртка"). Она устроена немного сложнее, чем map и filter, но, в целом, сохраняет общий подход с передачей функции.

Как обычно, начнём с примера на циклах. Реализуем код, находящий самого взрослого пользователя.

<?php
$users = [
    ['name' => 'Igor', 'age' => 19],
    ['name' => 'Danil', 'age' => 4],
    ['name' => 'Vovan', 'age' => 4],
    ['name' => 'Matvey', 'age' => 16],
];

$oldest = $users[0];
foreach ($users as $user) {
    if ($user['age'] > $oldest['age']) {
        $oldest = $user;
    }
}
print_r($oldest); // => ['name' => 'Igor', 'age' => 19]

Основное отличие агрегации от отображения и фильтрации в том, что результатом агрегации может быть любой тип данных — как примитивный, так и составной, например, массив. Кроме того, агрегация нередко подразумевает инициализацию начальным значением. В примере выше она выполняется на строчке $oldest = $users[0];.

Посмотрим ещё один пример агрегации — группировку имён пользователей по возрасту:

<?php
$users = [
    ['name' => 'Petr', 'age' => 4],
    ['name' => 'Igor', 'age' => 19],
    ['name' => 'Vovan', 'age' => 4],
    ['name' => 'Matvey', 'age' => 16],
];

$usersByAge = [];
foreach ($users as $user) {
    $usersByAge[$user['age']][] = $user['name'];
}
print_r($usersByAge);
# => Array
# (
#     [4] => Array
#         (
#             [0] => Petr
#             [1] => Vovan
#         )
#
#     [19] => Array
#         (
#             [0] => Igor
#         )
#
#     [16] => Array
#         (
#             [0] => Matvey
#         )
#
# )

В этом примере результатом агрегации становится массив массивов, который в самом начале инициируется пустым массивом. Значение, которое накапливает результат агрегации, принято называть словом "аккумулятор". В примерах выше это $oldest и $usersByAge.

Реализуем первый пример, используя array_reduce.

<?php

$oldest = array_reduce($users, function ($acc, $user) {
    return $user['age'] > $acc['age'] ? $user : $acc;
}, $users[0]);
print_r($oldest); // => ['name' => 'Igor', 'age' => 19]

Функция array_reduce принимает на вход три параметра. Два из них уже традиционны — это коллекция и функция-обработчик, а вот третьим выступает начальное значение аккумулятора. Поиск самого взрослого пользователя аналогичен поиску максимального (или минимального) числа в массиве. Соответственно, аккумулятор должен быть инициализирован первым пользователем. Этот же аккумулятор возвращается наружу.

Callback-функция, передаваемая в array_reduce — самая важная часть и ключ к пониманию работы всего механизма агрегации. Она принимает на вход два значения. Первый — текущее значение аккумулятора, второй — текущий обрабатываемый элемент. Задача функции — вернуть новое значение аккумулятора. array_reduce никак не анализирует содержимое аккумулятора. Всё, что она делает, передаёт значение аккумулятора в каждый новый вызов до тех пор, пока не будет обработана вся коллекция, и в конце концов вернёт его наружу. Подчеркну, что возвращать аккумулятор надо всегда, даже если он не изменился.

Второй пример с использованием array_reduce выглядит так:

<?php

$usersByAge = array_reduce($users, function ($acc, $user) {
    $acc[$user['age']][] = $user['name'];
    return $acc;
}, []);
print_r($usersByAge);

Код практически не изменился, за исключением того, что ушёл цикл и появился возврат аккумулятора из анонимной функции.

Реализация

<?php

function myReduce($coll, callable $callback, $init = null)
{
    $acc = $init;
    foreach ($coll as $item) {
        $acc = $callback($acc, $item); // Заменяем старый аккумулятор новым
    }
    return $acc;
}

array_reduce — очень мощная функция. Формально, можно работать, используя одну лишь её, так как она может заменить и отображение и фильтрацию. Но делать так не стоит. Агрегация управляет состоянием (аккумулятором) явно. Такой код всегда сложнее и требует больше действий. Поэтому, если задачу возможно решить отображением или фильтрацией, то так и нужно делать.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
900
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 120 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы PHP-разработчик
Профессия
Разработка веб-приложений на Laravel
20 октября 8 месяцев

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг»