Карта, Фильтр, Уменьшение


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

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

карта

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

map(func, *iterables)

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

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

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

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

my_pets = ['alfred', 'tabitha', 'william', 'arla'] uppered_pets = [] for pet in my_pets: pet_ = pet.upper() uppered_pets.append(pet_) print(uppered_pets)
1
2
3
4
5
6
7
8
my_pets = [ 'альфред' , 'табита' , 'уильям' , 'арла' ]
uppered_pets = [ ]
для питомца в my_pets :
pet_ = домашнее животное . верхний ( )
uppered_pets . добавить ( pet_ )
печать ( uppered_pets )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

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

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

# Python 3 my_pets = ['alfred', 'tabitha', 'william', 'arla'] uppered_pets = list(map(str.upper, my_pets)) print(uppered_pets)
1
2
3
4
5
6
# Python 3
my_pets = [ 'альфред' , 'табита' , 'уильям' , 'арла' ]
uppered_pets = список ( карта ( ул . Верхняя , my_pets ))
печать ( uppered_pets )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

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

Что еще более важно отметить, так это то, что по определению str.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)
1
2
3
4
5
6
7
# Python 3
circle_areas = [ 3.56773 , 5.57668 , 4.00914 , 56.24241 , 9
.01344 , 32.00013 ]
результат = список ( карта ( круглая , окружность_областей , диапазон ( 1 , 7 )))
печать ( результат )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Увидеть красоту 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)
1
2
3
4
5
6
7
# Python 3
circle_areas = [ 3.56773 , 5.57668 , 4.00914 , 56.24241 , 9
.01344 , 32.00013 ]
результат = список ( карта ( круглая , окружность_областей , диапазон ( 1 , 3 )))
печать ( результат )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

То же самое происходит, если 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)
1
2
3
4
5
6
7
8
# Python 3
my_strings = [ 'a' , 'b' , 'c' , 'd' , 'e' ]
my_numbers = [ 1 , 2 , 3 , 4 , 5 ]
результаты = список ( zip ( my_strings , my_numbers ))
печать ( результаты )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

В качестве бонуса, вы можете догадаться , что произойдет в вышеуказанной сессии , если 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)
1
2
3
4
5
6
7
8
# Python 3
my_strings = [ 'a' , 'b' , 'c' , 'd' , 'e' ]
my_numbers = [ 1 , 2 , 3 , 4 , 5 ]
results = list ( map ( лямбда x , y : ( x , y ) , my_strings ,
my_numbers ))
печать ( результаты )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

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

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

Это все о карте. Наfilter()

Фильтр

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

filter(func, iterable)

Следует отметить следующие моменты, касающиеся filter():

  1. В отличие от map()этого требуется только одна итерация.
  2. funcАргумент должен возвращать булево тип. Если это не так, filterпросто возвращает ему 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)
1
2
3
4
5
6
7
8
9
# Python 3
баллы = [ 66 , 90 , 68 , 59 , 76 , 60 , 88 , 74 , 81 , 65 ]
def is_A_student ( оценка ) :
обратный счет > 75
over_75 = список ( фильтр ( is_A_student , оценки ))
печать ( over_75 )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

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

# Python 3 dromes = ("demigod", "rewire", "madam", "freer", "anutforajaroftuna", "kiosk") palindromes = list(filter(lambda word: word == word[::-1], dromes)) print(palindromes)
1
2
3
4
5
6
# Python 3
dromes = ( "полубог" , "перепрограммировать" , "мадам" , "свободнее" ,
«анутфораджарофтуна» , «киоск» )
палиндромы = список ( фильтр ( лямбда- слово : слово == слово [
:: - 1 ] , дромы ))
печать ( палиндромы )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

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

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

Уменьшать

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

reduce(func, iterable[, initial])

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

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

Давайте создадим нашу собственную версию встроенной sum()функции Python . 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)
1
2
3
4
5
6
# Python 3
из functools import уменьшить
числа = [ 3 , 4 , 6 , 9 , 34 , 12 ]
def custom_sum ( первый , второй ) :
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Результат, как и следовало ожидать, есть 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)
1
2
3
4
5
6
# Python 3
из functools import уменьшить
числа = [ 3 , 4 , 6 , 9 , 34 , 12 ]
def custom_sum ( первый , второй ) :
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

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

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

Упражнение

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