Функции Map
, Filter
и Reduce
являются парадигмами функционального программирования. Они позволяют программисту (вам) писать более простой и короткий код без необходимости беспокоиться о таких сложностях, как циклы и ветвления.
По сути, эти три функции позволяют вам применять функцию ко многим итерациям за один полный цикл. Функции map
и filter
встроены в Python (в модуле __builtins__
) и не требуют импорта. reduce
, однако, необходимо импортировать, поскольку он находится в модуле functools
.Давайте получше разберемся, как они все работают, начиная с map
.
Функция Map
Функция map()
в python имеет следующий синтаксис:
map(func, *iterables)
Где func
это функция, к которой будет применен каждый элемент из iterables
(столько, сколько их есть). Заметили звездочку (*
) на iterables
? Это означает, что итераций может быть сколько угодно много, поскольку у func
столько точных чисел, сколько и для входных аргументов. Прежде чем перейти к примеру, важно отметить следующее:
- В Python 2, функция
map()
возвращает список. В Python 3, однако, функция возвращаетmap object
который является объектом-генератором. Чтобы получить результат в виде списка, встроенная функцияlist()
может быть вызвана для объекта карты, то есть списокlist(map(func, *iterables))
- Количество аргументов функции должно быть числом перечисленных
iterables
.
Давайте посмотрим, как эти правила действуют на следующих примерах.
Скажем, у меня есть список (iterable
) моих любимых имен домашних животных, все в нижнем регистре, и мне нужны они в верхнем регистре. Традиционно, в обычном программировании на Python я бы сделал что-то вроде этого:
что затем выведет ['САША', 'МАША', 'ВАДИМ', 'ОЛЬГА']
С функциями map()
это не только проще, но и гораздо более гибко. Вы просто делаете следующее:
# Python 3
my_pets = ['саша', 'маша', 'вадим', 'ольга']
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()
, которая принимает два аргумента - число для округления и число десятичных разрядов для округления числа.Итак, поскольку функция требует наличия двух аргументов, нам нужно передать два итерируемых.
Видите красоту 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]
.
Давайте, попробуйте, сами
То же самое происходит, если circle_areas
меньше длины второго итерируемого. Python просто останавливается, когда не может найти следующий элемент в одном из итерируемых.
Чтобы закрепить наши знания о функции map()
мы собираемся использовать ее для реализации собственной пользовательской функции zip()
. Функция zip()
это функция, которая принимает несколько итерируемых, а затем создает кортеж, содержащий каждый из элементов в итерируемых.
Как и функция map()
, в Python 3 он возвращает объект-генератор, который можно легко преобразовать в список, вызвав для него встроенную функцию list
. Используйте сеанс интерпретатора ниже, чтобы получить представление о zip()
прежде чем мы создадим наш собственный с помощью map()
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()
!
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()
:
- В отличие от функции
map()
- функцииfilter
, требуется только один итерируемый аргумент. - Аргумент
func
необходим для возврата логического типа. Если этого не происходит, фукцияfilter
sпросто возвращает передаваемый емуiterable
. Кроме того, поскольку требуется только один итерируемый, подразумевается, чтоfunc
должен принимать только один аргумент. filter
пропускает каждый элемент в итерируемом черезfunc
возвращает только только те, которые имеют значение true. Ведь это же заложено в самом названии -- «фильтр».
Давайте посмотрим несколько примеров функции filter
.
Ниже приведен список (iterable
) баллов 10 студентов на экзамене по химии. Давайте отфильтруем тех, кто сдал с баллом выше 75 ... используя filter
.
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подозреваемых палиндромов.
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()
должно быть отмечено следующее:
func
требуется два аргумента, первый из которых является первым элементом вiterable
(еслиinitial
не указан) а второй - вторым элементом вiterable
. Еслиinitial
указано, то оно становится первым аргументом функцииfunc
, а первый элемент вiterable
становится вторым элементом.- Функция
reduce
"уменьшает"iterable
до одного значения.
Как обычно, давайте рассмотрим несколько примеров функции reduce()
.
Давайте создадим нашу собственную версию встроенной функции sum()
. Функция sum()
возвращает сумму всех элементов итерируемого объекта, переданных ей.
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
.
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
чтобы исправить неверный код:
- Используя функцию map() печатайте квадрат, каждого числа округленный до 3х знаков
- Используя функцию filter() печатайте только те имена, у которых меньше 7 букв
- используя функцию reduce() напечатайте произведение этих чисел
Правильное решение ниже:
#### Map
from functools import reduce
my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59]
my_names = ["олимпиада", "закат", "программа", "python", "код"]
my_numbers = [2, 7, 10, 21, 8]
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)