Python

Вы когда-нибудь смотрели на свой код и видели водопад из циклов 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. Они имеют право быть в вашем коде, но расширение инструментария никогда не бывает лишним.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Jonathan Hsu: How To Replace Your Python For Loops with Map, Filter, and Reduce

Предыдущая статьяREST API для приложения со Spring Boot, Kotlin и Gradle
Следующая статьяОт Spotify к собственной рекомендательной системе