Функция reduce модуля functools

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Смотреть материал на YouTube | RuTube

Следующая весьма полезная функция reduce модуля functools позволяет последовательно обрабатывать элементы итерируемых объектов, таких как, списки, кортежи, строки, словари, множества и т.п. Давайте изучим порядок ее работы на конкретных примерах.

Предположим, нам необходимо просуммировать значения следующего списка:

data = [7, 2, 5, -3, 4]

Конечно, мы легко можем это сделать с помощью функции sum() языка Python:

res = sum(data)  # 15

Но мы реализуем эту логику с помощью функции reduce(). Эта функция первым аргументом ожидаем ссылку на функцию двух параметров и возвращающую одно вычисленное значение. Второй обязательный аргумент у reduce() – это итерируемый объект. Вначале объявим функцию для сложения элементов списка:

def reduce_sum(a, b):
    return a + b

И вызовем непосредственно функцию reduce() следующим образом:

res = functools.reduce(reduce_sum, data)

В переменной res будет храниться значение 15 – сумма всех элементов списка data. Как получился такой результат? Давайте разберемся.

Вначале функция reduce() вызывает переданную ей функцию reduce_sum с аргументами a=7, b=2, то есть, выбираются первые два значения из списка data. Функция reduce_sum() суммирует эти величины, и результат сохраняется в некоторой переменной, пусть это будет переменная r. На следующей итерации берется следующее значение b=5, а в параметр ‘a’ передается ранее вычисленное значение r. В результате, к сумме двух элементов добавляется следующее значение 5. Получаем результат сложения трех чисел r=14. Этот процесс вычислений продолжается, пока не будет достигнут конец списка data. Итоговый результат r=15 возвращается, как результат работы функции reduce().

Конечно, когда передаваемая функция reduce_sum примитивна (суммирует два числа), вместо нее удобнее использовать лямбда-функцию:

res = functools.reduce(lambda a, b: a + b, data)

Результат будет абсолютно тем же самым. Если же вместо сложения прописать операцию умножения двух величин:

res = functools.reduce(lambda a, b: a * b, data)

то будет вычисляться произведение элементов списка data. Или можно реализовать логику поиска наибольшего значения:

res = functools.reduce(lambda a, b: a if a > b else b, data)

По аналогии можно прописывать и более сложные вычисления, для которых нет аналогов в стандартных функциях языка Python. Например, сумму модулей:

res = functools.reduce(lambda a, b: abs(a) + abs(b), data)

Или объединение строк с их предобработкой:

cities = ["уфа ", "  казань ", "омск", "  ярославль"]
res = functools.reduce(lambda a, b: a.strip().title() + ";" + b. strip().title(), cities)

И так далее. Везде, где происходит накопление данных, при обработке последовательностей, можно попробовать реализовать это с помощью функции reduce().

А как будет себя вести эта функция, если в списке только один элемент? Давайте посмотрим, выполним программу:

def reduce_sum(a, b):
    print(f"{a, b}")
    return a + b
 
 
res = functools.reduce(reduce_sum, [1])

Видим, что функция reduce_sum ни разу не была вызвана. Следовательно, при одном единственно элементе, он просто возвращается. Но, если следующим аргументом указать некоторое начальное значение суммы, например:

res = functools.reduce(reduce_sum, [1], 10)

то функция reduce_sum будет вызвана, увидим строчку:

(10, 1)

то есть параметр a=10, b=1 и итоговая сумма равна 11. Таким образом, переданная функция вызывается только в том случае, если имеются, как минимум два значения.

Если же вызывается reduce() для пустой последовательности:

res = functools.reduce(reduce_sum, [])

то генерируется исключение TypeError. Но, при указании начального значения:

res = functools.reduce(reduce_sum, [], 2)

оно будет возвращаться функцией reduce() без вызова функции reduce_sum, т.к. здесь нет второго значения для параметра b.

Вот такие особенности работы функции reduce() следует иметь в виду при ее использовании в своих программах. Самое главное правильно формировать передаваемую функцию. В качестве последнего примера приведу использование функции reduce() для определения наличия хотя бы одного четного значения в списке data:

data = [7, 21, 5, -3, 3]
res = functools.reduce(lambda a, b: a or b % 2 == 0, data, False)

Разберитесь детально в порядке ее работы и в качестве домашнего задания реализуйте по аналогии определения наличия всех четных элементов в списке data.

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Видео по теме