Get started learning Python with DataCamp's free Intro to Python tutorial. Learn Data Science by completing interactive coding challenges and watching videos by expert instructors. Start Now!
This site is generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join 11 million other learners and get started learning Python for data science today!
Good news! You can save 25% off your Datacamp annual subscription with the code LEARNPYTHON23ALE25 - Click here to redeem your discount
Карта, Фільтр, Зведення
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
, який є генератором. Щоб отримати результат у вигляді списку, на map об'єкт можна викликати вбудовану функціюlist()
, тобтоlist(map(func, *iterables))
. - Кількість аргументів у
func
має бути рівною кількості перерахованих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
за визначенням вимагає лише одного аргументу, тому ми передали лише одну ітерабельну. Отже, якщо функція, яку ви передаєте, вимагає двох або трьох, або 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
.
Мабуть, ви задумалися: "Що буде, якщо я передам iterable коротшу або довшу, ніж перший iterable? Тобто, що буде, якщо я передам range(1, 3)
чи range(1, 9999)
як другий iterable у наведеній вище функції?". І відповідь проста: нічого! Добре, це не зовсім так. "Нічого" в сенсі того, що функція 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
менше за довжину другого iterable. Python просто зупиняється, коли не може знайти наступний елемент в одному з iterable.
Щоб закріпити наші знання про функцію 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()
пропускає кожен елемент з iterable через функцію і повертає результат усіх елементів, які пройшли через функцію, filter()
, насамперед вимагає, щоб функція повертала булеві значення (true або false), і потім пропускає кожен елемент з iterable через функцію, "фільтруючи" ті, що є false. Вона має наступний синтаксис:
filter(func, iterable)
Слід звернути увагу на наступне щодо filter()
:
- На відміну від
map()
, потрібно лише одну ітерабельну. - Аргумент
func
повинен повертати булевий тип. Якщо ні,filter
просто повертає передану йому ітерабельну. Також, оскільки потрібна лише одна ітерабельна, зрозуміло, щоfunc
має приймати лише один аргумент. filter
пропускає кожен елемент з iterable через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
) підозрюваних паліндромів.
# 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
до одного значення.
Як завжди, давайте розглянемо кілька прикладів.
Давайте створимо власну версію вбудованої функції Python sum()
. Функція sum()
повертає суму всіх елементів в переданому їй iterable.
# 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's Map, Reduce і Filter. Виконайте наведені нижче вправи, щоб підтвердити ваше розуміння кожної функції.
Exercise
У цій вправі ви використаєте кожну з функцій map
, filter
і reduce
, щоб виправити помилки в коді.
from functools import reduce
# Use map to print the square of each numbers rounded
# to three 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.")
This site is generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join over a million other learners and get started learning Python for data science today!