HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Последняя функция из нашей тройки ФВП - reduce() (говорят "свертка"), которая используется для<strong>агрегации</strong>данных. Под агрегацией понимается операция, вычисляющая значение, зависящее от всего набора данных. К таким операциям, например, относятся нахождение среднего значения, суммы элементов, большего или меньшего. Этот подход разбирался в<a>курсе по спискам</a>.</p>
1 <p>Последняя функция из нашей тройки ФВП - reduce() (говорят "свертка"), которая используется для<strong>агрегации</strong>данных. Под агрегацией понимается операция, вычисляющая значение, зависящее от всего набора данных. К таким операциям, например, относятся нахождение среднего значения, суммы элементов, большего или меньшего. Этот подход разбирался в<a>курсе по спискам</a>.</p>
2 <p>reduce() устроен немного сложнее, чем map() и filter(), но, в целом, сохраняет общий подход с передачей функции. Реализуем код, находящий общее количество денег у группы людей. Здесь сразу прослеживается агрегация, нам нужно свести количество денег всех пользователей к одному значению:</p>
2 <p>reduce() устроен немного сложнее, чем map() и filter(), но, в целом, сохраняет общий подход с передачей функции. Реализуем код, находящий общее количество денег у группы людей. Здесь сразу прослеживается агрегация, нам нужно свести количество денег всех пользователей к одному значению:</p>
3 <p>Основное отличие агрегации от отображения и фильтрации в том, что результатом агрегации может быть любой тип данных - как примитивный, так и составной, например, список. Кроме того, агрегация нередко подразумевает инициализацию начальным значением, которое принято называть аккумулятором. В примере выше она выполняется на строчке sum = 0. Здесь переменная sum "аккумулирует" результат внутри себя.</p>
3 <p>Основное отличие агрегации от отображения и фильтрации в том, что результатом агрегации может быть любой тип данных - как примитивный, так и составной, например, список. Кроме того, агрегация нередко подразумевает инициализацию начальным значением, которое принято называть аккумулятором. В примере выше она выполняется на строчке sum = 0. Здесь переменная sum "аккумулирует" результат внутри себя.</p>
4 <p>Посмотрим еще один пример агрегации - группировку имён пользователей по возрасту:</p>
4 <p>Посмотрим еще один пример агрегации - группировку имён пользователей по возрасту:</p>
5 <p>В этом примере результатом агрегации становится словарь, в значениях которого записаны списки. Этот результат в самом начале инициируется пустым словарем, а затем постепенно, на каждой итерации, "наполняется" нужными данными. Значение, которое накапливает результат агрегации, принято называть словом "аккумулятор". В примерах выше это sum и users_by_age.</p>
5 <p>В этом примере результатом агрегации становится словарь, в значениях которого записаны списки. Этот результат в самом начале инициируется пустым словарем, а затем постепенно, на каждой итерации, "наполняется" нужными данными. Значение, которое накапливает результат агрегации, принято называть словом "аккумулятор". В примерах выше это sum и users_by_age.</p>
6 <p>Реализуем первый пример, используя reduce():</p>
6 <p>Реализуем первый пример, используя reduce():</p>
7 <p>Функция reduce() принимает на вход три параметра - функцию-обработчик, коллекцию и начальное значение аккумулятора. Этот же аккумулятор возвращается наружу в качестве результата всей операции. В отличие от map() и filter(), которые используют ленивые вычисления, reduce() сразу возвращает результат.</p>
7 <p>Функция reduce() принимает на вход три параметра - функцию-обработчик, коллекцию и начальное значение аккумулятора. Этот же аккумулятор возвращается наружу в качестве результата всей операции. В отличие от map() и filter(), которые используют ленивые вычисления, reduce() сразу возвращает результат.</p>
8 <p>Функция, передаваемая в reduce() - самая важная часть и ключ к пониманию работы всего механизма агрегации. Она принимает на вход два значения. Первое - текущее значение аккумулятора, второе - текущий обрабатываемый элемент. Задача функции - вернуть новое значение аккумулятора. reduce() никак не анализирует содержимое аккумулятора. Всё, что она делает, передаёт его в каждый новый вызов до тех пор, пока не будет обработана вся коллекция, и в конце концов вернёт его наружу. Подчеркну, что возвращать аккумулятор надо всегда, даже если он не изменился.</p>
8 <p>Функция, передаваемая в reduce() - самая важная часть и ключ к пониманию работы всего механизма агрегации. Она принимает на вход два значения. Первое - текущее значение аккумулятора, второе - текущий обрабатываемый элемент. Задача функции - вернуть новое значение аккумулятора. reduce() никак не анализирует содержимое аккумулятора. Всё, что она делает, передаёт его в каждый новый вызов до тех пор, пока не будет обработана вся коллекция, и в конце концов вернёт его наружу. Подчеркну, что возвращать аккумулятор надо всегда, даже если он не изменился.</p>
9 <p>Приведем еще один пример использования reduce(). На этот раз сгруппируем пользователей по возрастам. Для этого будем собирать данные в аккумулятор-коллекцию:</p>
9 <p>Приведем еще один пример использования reduce(). На этот раз сгруппируем пользователей по возрастам. Для этого будем собирать данные в аккумулятор-коллекцию:</p>
10 <p>Разберем пошагово работу функции reduce(). В функцию передается колбек, который принимает два параметра acc и user. Чтобы лучше понять работу, нужно проследить чему равны значения этих параметров на каждой итерации:</p>
10 <p>Разберем пошагово работу функции reduce(). В функцию передается колбек, который принимает два параметра acc и user. Чтобы лучше понять работу, нужно проследить чему равны значения этих параметров на каждой итерации:</p>
11 <ol><li>На первой итерации acc равен пустому словарю, это начальное значение аккумулятора задается последним параметром reduce(group_by_age, users, {}) - здесь передается пустой словарь. Параметр user равен первому элементу списка, то есть { 'name': 'Petr', age: 4 }. В пустом словаре создается список под ключом user['age'] и в этот список добавляется текущее имя пользователя. В итоге acc становится равен словарю { 4: ['Petr'] }. Из функции возвращается acc - это значение будет аккумулятором на следующей итерации</li>
11 <ol><li>На первой итерации acc равен пустому словарю, это начальное значение аккумулятора задается последним параметром reduce(group_by_age, users, {}) - здесь передается пустой словарь. Параметр user равен первому элементу списка, то есть { 'name': 'Petr', age: 4 }. В пустом словаре создается список под ключом user['age'] и в этот список добавляется текущее имя пользователя. В итоге acc становится равен словарю { 4: ['Petr'] }. Из функции возвращается acc - это значение будет аккумулятором на следующей итерации</li>
12 <li>На второй итерации acc равен значению, которое вернулось из предыдущей итерации, это словарь { 4: ['Petr'] }. Параметр user равен второму элементу списка { 'name': 'Igor', age: 19 }. В аккумуляторе acc нет ключа с возрастом текущего пользователя, поэтому добавляется новый ключ и список. После заполнения acc равен { 4: ['Petr'], 19: ['Igor'] }, этот словарь возвращается из функции</li>
12 <li>На второй итерации acc равен значению, которое вернулось из предыдущей итерации, это словарь { 4: ['Petr'] }. Параметр user равен второму элементу списка { 'name': 'Igor', age: 19 }. В аккумуляторе acc нет ключа с возрастом текущего пользователя, поэтому добавляется новый ключ и список. После заполнения acc равен { 4: ['Petr'], 19: ['Igor'] }, этот словарь возвращается из функции</li>
13 <li>На этой итерации acc равен словарю, вернувшемуся из прошлой итерации { 4: ['Petr'], 19: ['Igor'] }. Параметр user равен { 'name': 'Ivan', age: 4 }. Значение user['age'] равно 4 - этот ключ уже имеется в аккумуляторе, поэтому новый ключ не создается, а текущий пользователь добавляется в существующий список. В итоге аккумулятор равен словарю { 4: ['Petr', 'Ivan'], 19: ['Igor'] } и он возвращается из функции</li>
13 <li>На этой итерации acc равен словарю, вернувшемуся из прошлой итерации { 4: ['Petr'], 19: ['Igor'] }. Параметр user равен { 'name': 'Ivan', age: 4 }. Значение user['age'] равно 4 - этот ключ уже имеется в аккумуляторе, поэтому новый ключ не создается, а текущий пользователь добавляется в существующий список. В итоге аккумулятор равен словарю { 4: ['Petr', 'Ivan'], 19: ['Igor'] } и он возвращается из функции</li>
14 <li>Последняя итерация. Параметр acc равен { 4: ['Petr', 'Ivan'], 19: ['Igor'] }, а user равен { 'name': 'Matvey', age: 16 }. Ключа 16 в аккумуляторе нет, поэтому добавляется новый список в ключ 16, в этот список добавляется текущий пользователь. В итоге acc будет равен { 4: ['Petr', 'Ivan'], 16: ['Matvey'], 19: ['Igor'] }, этот словарь возвращается и в итоге будет результатом работы всего редьюса, так как это последняя итерация</li>
14 <li>Последняя итерация. Параметр acc равен { 4: ['Petr', 'Ivan'], 19: ['Igor'] }, а user равен { 'name': 'Matvey', age: 16 }. Ключа 16 в аккумуляторе нет, поэтому добавляется новый список в ключ 16, в этот список добавляется текущий пользователь. В итоге acc будет равен { 4: ['Petr', 'Ivan'], 16: ['Matvey'], 19: ['Igor'] }, этот словарь возвращается и в итоге будет результатом работы всего редьюса, так как это последняя итерация</li>
15 </ol><p>reduce() - очень мощная функция. Формально, можно работать, используя только ее, так как она может заменить и отображение, и фильтрацию. Но делать так не стоит. Агрегация управляет состоянием (аккумулятором) явно. Такой код всегда сложнее и требует больше действий. Поэтому, если задачу возможно решить отображением или фильтрацией, то так и нужно делать.</p>
15 </ol><p>reduce() - очень мощная функция. Формально, можно работать, используя только ее, так как она может заменить и отображение, и фильтрацию. Но делать так не стоит. Агрегация управляет состоянием (аккумулятором) явно. Такой код всегда сложнее и требует больше действий. Поэтому, если задачу возможно решить отображением или фильтрацией, то так и нужно делать.</p>
16 <h2>Как думать о редьюсе</h2>
16 <h2>Как думать о редьюсе</h2>
17 <p>Распишем алгоритм, который поможет правильно подступаться к задачам с использованием редьюс. Представьте, что перед вами список курсов с уроками внутри них и вам нужно посчитать количество всех уроков. Например, такое может быть нужно для вычисления длительности программы обучения. На Хекслете подобные задачи встречаются регулярно.</p>
17 <p>Распишем алгоритм, который поможет правильно подступаться к задачам с использованием редьюс. Представьте, что перед вами список курсов с уроками внутри них и вам нужно посчитать количество всех уроков. Например, такое может быть нужно для вычисления длительности программы обучения. На Хекслете подобные задачи встречаются регулярно.</p>
18 <p>Здесь мы видим два курса, в которых суммарно 5 уроков. Попробуем теперь высчитать это число программно. Первый вопрос, на который надо ответить, является ли данная операция агрегацией? Ответ - Да, так как мы сводим исходные данные, к какому-то вычисляемому результату. Дальше смотрим, чем является результат операции. В нашем случае это число, которое вычисляется как сумма уроков в каждом курсе. Значит начальным значением аккумулятора будет 0.</p>
18 <p>Здесь мы видим два курса, в которых суммарно 5 уроков. Попробуем теперь высчитать это число программно. Первый вопрос, на который надо ответить, является ли данная операция агрегацией? Ответ - Да, так как мы сводим исходные данные, к какому-то вычисляемому результату. Дальше смотрим, чем является результат операции. В нашем случае это число, которое вычисляется как сумма уроков в каждом курсе. Значит начальным значением аккумулятора будет 0.</p>
19 <p>Теперь примерный алгоритм:</p>
19 <p>Теперь примерный алгоритм:</p>
20 <ol><li>Инициализируем накапливаемый результат нулем</li>
20 <ol><li>Инициализируем накапливаемый результат нулем</li>
21 <li>Обходим коллекцию курсов по одному<ul><li>Прибавляем к аккумулятору количество уроков в текущем курсе</li>
21 <li>Обходим коллекцию курсов по одному<ul><li>Прибавляем к аккумулятору количество уроков в текущем курсе</li>
22 </ul></li>
22 </ul></li>
23 </ol><p>Этот алгоритм будет идентичным в любом варианте решения, как через цикл, так и через редьюс:</p>
23 </ol><p>Этот алгоритм будет идентичным в любом варианте решения, как через цикл, так и через редьюс:</p>
24 <h2>Реализация</h2>
24 <h2>Реализация</h2>
25 <p>Напишем свою собственную функцию my_reduce(), работающую аналогично библиотечному reduce():</p>
25 <p>Напишем свою собственную функцию my_reduce(), работающую аналогично библиотечному reduce():</p>
26  
26