Начните изучать Python с помощью бесплатного курса Введение в Python от онлайн-института Кодкамп.

Map, Filter, Reduce


Map, Filter и Reduce являются парадигмами функционального программирования. Они позволяют программисту (вам) писать более простой и короткий код без необходимости беспокоиться о таких сложностях, как циклы и ветвления.

По сути, эти три функции позволяют вам применять функцию ко многим итерациям за один полный цикл. map и filter встроены в Python (в модуле __builtins__) и не требуют импорта. reduce, однако, необходимо импортировать, поскольку он находится в модуле functools . Давайте получше разберемся, как они все работают, начиная с map.

Map

Функция map() в python имеет следующий синтаксис:

map(func, *iterables)

Где func это функция, к которой будет применен каждый элемент из iterables (столько, сколько их есть). Заметили звездочку(*) на iterables? Это означает, что итераций может быть сколько угодно много, поскольку у func столько точных чисел, сколько и для входных аргументов. Прежде чем перейти к примеру, важно отметить следующее:

  1. В Python 2, функция map() возвращает список. В Python 3, однако, функция возвращает map object который является объектом-генератором. Чтобы получить результат в виде списка, встроенная функция list() может быть вызвана для объекта карты, то есть список list(map(func, *iterables))
  2. Количество аргументов функции должно быть числом перечисленных iterables.

Давайте посмотрим, как эти правила действуют на следующих примерах.

Скажем, у меня есть список (iterable) моих любимых имен домашних животных, все в нижнем регистре, и мне нужны они в верхнем регистре. Традиционно, в обычном программировании на Python я бы сделал что-то вроде этого:

my_pets = ['alfred', 'tabitha', 'william', 'arla']
uppered_pets = []

for pet in my_pets:
    pet_ = pet.upper()
    uppered_pets.append(pet_)

print(uppered_pets)

что затем выведет ['ALFRED', 'TABITHA', 'WILLIAM', 'ARLA']

С функциями map() это не только проще, но и гораздо более гибко. Я просто делаю следующее:

# Python 3
my_pets = ['alfred', 'tabitha', 'william', 'arla']

uppered_pets = list(map(str.upper, my_pets))

print(uppered_pets)

Что также вывело бы тот же результат. Обратите внимание, что при использовании описанного выше синтаксиса map(), func в этом случае является str.upper и iterables этот список my_pets -- всего одна итерация.Также обратите внимание, что мы не вызывали функцию str.upper (делая это: str.upper()), так как функция map делает это за нас для каждого элемента в списке my_pets.

Более важно отметить, что функция str.upper upper требует только одного аргумента по определению, и поэтому мы передали ему только один итерируемый. Итак, если передаваемая функция требует двух, трех или n аргументов, вам нужно передать ей две, три или n итерируемого. Позвольте мне пояснить это на другом пример.

Скажем, у меня есть список областей круга, которые я где-то рассчитал, все с пятью десятичными знаками. И мне нужно округлить каждый элемент в списке до количества десятичных разрядов, равному его месту в списк. Это значает, что я должен округлить первый элемент в списке до одного десятичного знака, второй элемент в списке до двух десятичных знаков, третий элемент в список до трех знаков после запятой и т. д. Это элементарно с функцией map(). Давайте посмотрим, как именно.

Python уже радует нас встроенной функцией round() , которая принимает два аргумента - число для округления и число десятичных разрядов для округления числа . Итак, поскольку функция требует наличия двух аргументов, нам нужно передать два итерируемых.

# Python 3

circle_areas = [3.56773, 5.57668, 4.00914, 56.24241, 9.01344, 32.00013]

result = list(map(round, circle_areas, range(1,7)))

print(result)

Видите красоту map()? Можете ли вы представить всю гибкость?

Функция range(1,7) выступает в качестве второго аргумента функции round (количество требуемых десятичных разрядов на одну итерацию). Таким образом, поскольку map итерирует через circle_areas, во время первой итерации первый элемент circle_areas, 3.56773 передается вместе с первым элементом range(1,7), 1 в round, что фактически делает его round(3.56773, 1). Во время второй итерации второй элемент circle_areas, 5.57668 вместе со вторым элементом range(1,7), 2 передается в round что переводит его в round(5.57668, 2). Это происходит до тех пор, пока не будет достигнут конец списка circle_areas.

Уверен, что вы задаетесь вопросом: «Что если я передам итерируемое меньше или больше, чем длина первого итерируемого? То есть, что если я передам range(1,3) или range(1, 9999) в качестве второго итерируемого в вышеуказанной функции». И ответ прост: ничего! Ладно, это не правда. «Ничего» не происходит в том смысле, что функция map() не будет вызывать каких-либо исключений, она будет просто перебирать элементы до тех пор, пока не сможет найти второй аргумент функции, после чего она просто останавливается и возвращает результат.

Так, например, если вы оцените result = list(map(round, circle_areas, range(1,3))), вы не получите никакой ошибки, даже если длина circle_areas и длина range(1,3) отличаются. Вместо этого Python делает следующее: он берет первый элемент circle_areas и первый элемент range(1,3) и передает его в round. round оценивает его, а затем сохраняет результат. Затем он переходит ко второй итерации, второму элементу circle_areas и второму элементу range(1,3), round сохраняет его снова. Теперь в третьей итерации (circle_areas имеет третий элемент), Python берет третий элемент circle_areas, а затем пытается получить третий элемент range(1,3) но так как range(1,3) не имеет третьего элемента, Python просто останавливается и возвращает результат, который в этом случае будет просто [3.6, 5.58].

Давай, попробуй.

# Python 3

circle_areas = [3.56773, 5.57668, 4.00914, 56.24241, 9.01344, 32.00013]

result = list(map(round, circle_areas, range(1,3)))

print(result)

То же самое происходит, если circle_areas меньше длины второго итерируемого. Python просто останавливается, когда не может найти следующий элемент в одном из итерируемых.

Чтобы закрепить наши знания о функции map() мы собираемся использовать ее для реализации собственной пользовательской функции zip(). Функция zip() это функция, которая принимает несколько итерируемых, а затем создает кортеж, содержащий каждый из элементов в итерируемых. Как и map(), в Python 3 он возвращает объект-генератор, который можно легко преобразовать в список, вызвав для него встроенную функцию list. Используйте сеанс интерпретатора ниже, чтобы получить представление о zip() прежде чем мы создадим наш собственный с помощью map()

# Python 3

my_strings = ['a', 'b', 'c', 'd', 'e']
my_numbers = [1,2,3,4,5]

results = list(zip(my_strings, my_numbers))

print(results)

В качестве бонуса, можете догадаться, что произойдет в предыдущем сеансе, если my_strings и my_numbers не имеют одинаковую длину? Нет? Попробуйте! Измените длину одного из них.

Перейдем к нашей собственной функции zip()!

# Python 3

my_strings = ['a', 'b', 'c', 'd', 'e']
my_numbers = [1,2,3,4,5]

results = list(map(lambda x, y: (x, y), my_strings, my_numbers))

print(results)

Вы только полюбуйтесь! У нас тот же результат, что и у zip.

Вы заметили, что мне даже не нужно было создавать функцию def my_function() стандартным способом? Вот какие гибкие map() и Python в целом! Я просто использовал lambda функцию. Это не означает, что использование стандартного метода определения функции (для def function_name()) не разрешено, оно все еще разрешено. Я просто предпочел писать меньше кода (будь "Pythonic").

Про map всё. Переходим к filter()

Filter

В то время как map() пропускает каждый элемент итерируемого через функцию и возвращает результат всех элементов, прошедших через функцию filter(), прежде всего, требует, чтобы функция возвращала логические значения (true или false), а затем передает каждый элемент итерируемого через функцию, «отфильтровывая» те, которые являются ложными. Имеет следующий синтаксис:

filter(func, iterable)

Следующие пункты должны быть отмечены относительно filter():

  1. В отличие от map(), (), требуется только один итерируемый.
  2. Аргумент func необходим для возврата логического типа. Если этого не происходит, filter sпросто возвращает передаваемый ему iterable. Кроме того, поскольку требуется только один итерируемый, подразумевается, что func должен принимать только один аргумент.
  3. filter пропускает каждый элемент в итерируемом через func возвращает только только те, которые имеют значение true. Ведь это же заложено в самом названии -- «фильтр».

Давайте посмотрим несколько примеров

Ниже приведен список (iterable) баллов 10 студентов на экзамене по химии. Давайте отфильтруем тех, кто сдал с баллом выше 75 ... используя filter.

# Python 3
scores = [66, 90, 68, 59, 76, 60, 88, 74, 81, 65]

def is_A_student(score):
    return score > 75

over_75 = list(filter(is_A_student, scores))

print(over_75)

Следующим примером будет детектор палиндрома. «Палиндром» - это слово, фраза или последовательность, которые читаются одинаково в обе стороны. Давайте отфильтруем слова, являющиеся палиндромами, из набора (iterable) oподозреваемых палиндромов.

# Python 3
dromes = ("demigod", "rewire", "madam", "freer", "anutforajaroftuna", "kiosk")

palindromes = list(filter(lambda word: word == word[::-1], dromes))

print(palindromes)

Который должен вывести ['madam', 'anutforajaroftuna'].

Довольно круто, да? Наконец, reduce()

Reduce

reduce применяет функцию двух аргументов кумулятивно к элементам итерируемого, необязательно начиная с начального аргумента. Имеет следующий синтаксис:

reduce(func, iterable[, initial])

Где func это функция, к которой кумулятивно применяется каждый элемент iterable, а initial необязательное значение, которое помещается перед элементами итерируемого в вычислении и служит значением по умолчанию, когда итерируемый объект пуст. О reduce()должно быть отмечено следующее: 1. func требуется два аргумента, первый из которых является первым элементом в iterable (если initial не указан) а второй - вторым элементом в iterable. Если initial указано, то оно становится первым аргументом функции func, а первый элемент в iterable становится вторым элементом. 2. reduce "уменьшает" iterable до одного значения.

Как обычно, давайте рассмотрим несколько примеров.

Давайте создадим нашу собственную версию встроенной функции sum(). Функция sum() возвращает сумму всех элементов итерируемого объекта, переданных ей.

# Python 3
from functools import reduce

numbers = [3, 4, 6, 9, 34, 12]

def custom_sum(first, second):
    return first + second

result = reduce(custom_sum, numbers)
print(result)

Результат, как ожидалось, равен 68.

Так что же случилось?

Как обычно, все дело в итерациях: reduce берет первый и второй элементы в numbers и передает их соответственно в custom_sum. custom_sum вычисляет их сумму и возвращает ее в reduce. Затем reduceпринимает этот результат и применяет его в качестве первого элемента к custom_sum и принимает следующий элемент (третий) в numbers в качестве второго элемента для custom_sum. Он делает это непрерывно (накопительно), пока numbers не будет исчерпан.

Посмотрим, что произойдет, когда я использую необязательное начальное значение initial.

# Python 3
from functools import reduce

numbers = [3, 4, 6, 9, 34, 12]

def custom_sum(first, second):
    return first + second

result = reduce(custom_sum, numbers, 10)
print(result)

Результат, ожидаемо, равен 78 потому что reduce изначально использует 10 в качестве первого аргумента для custom_sum.

Это все относительно Python, Map, Reduce и Filter. Попробуйте выполнить приведенные ниже упражнения, чтобы лучше понять каждую функцию.

Упражнение

В этом упражнении вы будете использовать map, filter, и reduce чтобы исправить неверный код.

from functools import reduce # Use map to print the square of each numbers rounded # to two decimal places my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59] # Use filter to print only the names that are less than # or equal to seven letters my_names = ["olumide", "akinremi", "josiah", "temidayo", "omoseun"] # Use reduce to print the product of these numbers my_numbers = [4, 6, 9, 23, 5] # Fix all three respectively. map_result = list(map(lambda x: x, my_floats)) filter_result = list(filter(lambda name: name, my_names, my_names)) reduce_result = reduce(lambda num1, num2: num1 * num2, my_numbers, 0) print(map_result) print(filter_result) print(reduce_result) #### Map from functools import reduce my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59] my_names = ["olumide", "akinremi", "josiah", "temidayo", "omoseun"] my_numbers = [4, 6, 9, 23, 5] map_result = list(map(lambda x: round(x ** 2, 3), my_floats)) filter_result = list(filter(lambda name: len(name) <= 7, my_names)) reduce_result = reduce(lambda num1, num2: num1 * num2, my_numbers) print(map_result) print(filter_result) print(reduce_result) test_output_contains("[18.922, 37.088, 10.562, 95.453, 4.666, 78.854, 21.068]") test_output_contains("['olumide', 'josiah', 'omoseun']") test_output_contains("24840") success_msg("Congrats! Nice work.")

Этот сайт поддерживается онлайн-институтом Кодкамп. Кодкамп предлагает интерактивные онлайн-курсы по Python для развития цифровых навыков. Присоединяйтесь к тысячам других студентов и начните изучать Python уже сегодня!

Copyright © pythontuts.ru