Вы когда-нибудь смотрели на свой код и видели водопад из циклов for
? Вам приходилось щурить глаза и наклоняться к монитору, чтобы рассмотреть его поближе?
Я с этим знаком.
Циклы for
— это швейцарский армейский нож для решения проблем, но когда дело доходит до чтениякода, их ошеломляющее количество мешает пониманию.
Три метода — map
, filter
и reduce
— помогают устранить манию цикла for
, предлагая функциональные альтернативы, которые описывают, почему вы повторяете. Реализация в JavaScript отличается от Python.
Мы кратко представим каждый из трех методов, выделим синтаксические различия между ними в JavaScript и Python, а затем приведем примеры преобразования общих циклов for
.
Что такое Map, Filter и Reduce?
Просматривая свой ранее написанный код, я понял, что в 95% случаев при циклическом переборе строк или массивов я делаю одно из следующих действий: сопоставляю (map) последовательность операторов с каждым значением, фильтрую (filter) значения, соответствующие определенным критериям, или сокращаю (reduce) набор данных до одного агрегированного значения.
Три метода говорят нам о том, что причина, по которой вы создаете цикл через итерируемый объект, часто попадает в одну из этих трех функциональных категорий:
- Map: применяет один и тот же набор шагов к каждому элементу, сохраняя результат.
- Filter: применяет критерии проверки, сохраняя элементы со значением
True
. - Reduce: возвращает значение, которое передается от элемента к элементу.
Что отличает Map / Filter / Reduce в Python?
В Python эти три метода существуют как функции, а не методы класса массива (Array
) или строки (String
). Это означает, что вместо my_array.map(function)
следовало бы писать map(function, my_list)
.
Кроме того, каждый метод потребует передачи функции, которая будет выполняться для каждого элемента. Часто функция записывается как анонимная (в JavaScript называемая “толстой” стрелочной функцией). Однако в Python часто используются лямбда-выражения.
Синтаксис лямбда-выражения и стрелочной функции на самом деле очень похож. Замените =>
на :
и убедитесь, что вы используете ключевое слово lambda
, а все остальное почти идентично:
// Стрелочная функция в JavaScript
const square = number => number * number;
// Лямбда-выражение в Python
square = lambda number: number * number
Одно из ключевых различий между ними в том, что стрелочные функции могут расширяться в полномасштабные функции с несколькими операторами, в то время как лямбда-выражения ограничены одним возвращаемым. Таким образом, при применении map()
, filter()
или reduce()
, если вам нужно выполнить несколько операций над каждым элементом, сначала определите свою функцию, а затем установите в качестве параметра:
def inefficientSquare(number):
result = number * number
return result
map(inefficientSquare, my_list)
map(inefficientSquare, my_list)
Замена циклов for
Ладно, перейдем к хорошим вещам. Вот три примера общих циклов for
, которые будут заменены на map
, filter
и reduce
. Наша программная задача: вычислить сумму квадратов нечетных чисел в списке.
Приведем пример с базовыми циклами for
.
Примечание: это чисто для демонстрации и может быть улучшено даже без map/filter/reduce
.
numbers = [1,2,3,4,5,6]
odd_numbers = []
squared_odd_numbers = []
total = 0
# Фильтрация нечетных значений
for number in numbers:
if number % 2 == 1:
odd_numbers.append(number)
# Возведение в квадрат всех нечетных значений
for number in odd_numbers:
squared_odd_numbers.append(number * number)
# Расчет суммы
for number in squared_odd_numbers:
total += number
# Расчет среднего
Преобразуем каждый шаг в одну из функций:
from functools import reduce
numbers = [1,2,3,4,5,6]
odd_numbers = filter(lambda n: n % 2 == 1, numbers)
squared_odd_numbers = map(lambda n: n * n, odd_numbers)
total = reduce(lambda acc, n: acc + n, squared_odd_numbers)
Необходимо выделить несколько важных моментов синтаксиса:
map()
иfilter()
доступны изначально, а функцияreduce()
нужно импортировать из библиотекиfunctools
в Python 3+.- Лямбда-выражение является первым аргументом во всех трех функциях, а итерируемый объект — вторым.
- Лямбда-выражение для
reduce()
требует двух аргументов: аккумулятора (значения, которое передается каждому элементу) и самого отдельного элемента.
Наилучшие пожелания удалиться от потока циклов for
. Они имеют право быть в вашем коде, но расширение инструментария никогда не бывает лишним.
Читайте также:
- Как автоматизировать электронную почту с помощью Python
- Python 3: 3 функции, которые следует помнить
- Метод опорных векторов: примеры на Python
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Jonathan Hsu: How To Replace Your Python For Loops with Map, Filter, and Reduce