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, and Reduce הן פרדיגמות של תכנות פונקציונלי. הן מאפשרות למתכנת (כן, לך) לכתוב קוד פשוט יותר וקצר יותר, ללא צורך להתמודד עם מורכבות כמו לולאות והתפצלויות.
בעצם, שלוש הפונקציות הללו מאפשרות ליישם פונקציה על מספר אוספי נתונים במכה אחת. map
ו-filter
מגיעות מובנות עם פייתון (במודול __builtins__
) ואינן מצריכות ייבוא. לעומת זאת, את reduce
צריך לייבא כי היא נמצאת במודול functools
. בואו נקבל הבנה טובה יותר על אופן פעולתן, מתחילים עם map
.
Map
לפונקציה map()
בפייתון יש את התחביר הבא:
map(func, *iterables)
כאשר func
היא הפונקציה שכל אלמנט ב-iterables
(כמה שהם יהיו) ייושם עליה. שימו לב לכוכב (*
) ב-iterables
? זה אומר שיכולים להיות כמה אוספים ככל האפשר, כל עוד ל-func
יש את אותו מספר הנדרש כארגומנטים של קלט. לפני שנעבור לדוגמה, חשוב שתשימו לב לדברים הבאים:
- בפייתון 2, הפונקציה
map()
מחזירה רשימה. לעומת זאת, בפייתון 3, הפונקציה מחזירהmap object
שהוא אובייקט גנרטור. כדי לקבל את התוצאה כרשימה, אפשר לקרוא לפונקציה המובניתlist()
על אובייקט המפה, כלומרlist(map(func, *iterables))
. - מספר הארגומנטים ל-
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)
שזה יוציא את ['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()
), כי פונקציית המפה עושה זאת עבורנו על כל אלמנט ברשימת my_pets
.
מה שחשוב עוד יותר לשים לב אליו הוא שפונקציית str.upper
דורשת רק ארגומנט אחד על פי ההגדרה ולכן העברנו רק אובייקט אחד. אז, אם הפונקציה שאתה מעביר דורשת שניים, שלושה או n ארגומנטים, אז עליך להעביר שניים, שלושה או n אוספים לפונקציה זו. תן לי להבהיר זאת עם דוגמה אחרת.
נאמר שיש לי רשימה של שטחים מעגליים שבהם חישבתי מספרים כלשהם, הכל בחמישה מקומות עשרוניים. ואני צריך לעגל כל אלמנט ברשימה למקומות העשרוניים של המיקום שלו, כלומר אני צריך לעגל את האלמנט הראשון ברשימה למקום עשרוני אחד, האלמנט השני ברשימה לשני מקומות עשרוניים, האלמנט השלישי ברשימה לשלושה מקומות עשרוניים, וכן הלאה. עם map()
זו חתיכה של עוגה. בואו נראה איך.
פייתון כבר מעניקה לנו פונקציה מובנית שנקראת 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)
שונים. במקום זאת, זה מה שפייתון עושה: היא לוקחת את האלמנט הראשון של circle_areas
והאלמנט הראשון של range(1,3)
ומעבירה אותו ל-round
. round
מחשבת אותו ואז שומרת את התוצאה. ואז היא עוברת לאיטרציה השנייה, האלמנט השני של circle_areas
והאלמנט השני של range(1,3)
, round
שומרת אותו שוב. כעת, באיטרציה השלישית (ל-circle_areas
יש אלמנט שלישי), פייתון לוקחת את האלמנט השלישי של circle_areas
ולאחר מכן מנסה לקחת את האלמנט השלישי של range(1,3)
אבל מכיוון של-range(1,3)
אין אלמנט שלישי, פייתון פשוט עוצרת ומחזירה את התוצאה, במקרה זה זה יהיה פשוט [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
היא קצרה מאורך האוסף השני. פייתון פשוט עוצרת כאשר היא לא יכולה למצוא את האלמנט הבא באחד מהאוספים.
כדי לחזק את הידע שלנו על פונקציית map()
, נשתמש בה כדי ליישם את הפונקציה המובנית שלנו, zip()
. הפונקציה zip()
היא פונקציה שלוקחת מספר אוספים ולאחר מכן יוצרת זוג המכיל כל אחד מהאלמנטים באוספים. כמו map()
, בפייתון 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
אינם באותו אורך? לא? נסה את זה! שנה את האורך של אחד מהם.
עכשיו ליישום הפונקציה המותאמת אישית שלנו!
# 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()
, ובכלל, פייתון! פשוט השתמשתי בפונקציה lambda
. זה לא אומר שלא ניתן להשתמש בשיטת ההגדרה הסטנדרטית של פונקציה (של def function_name()
), עדיין אפשר. האם אני פשוט העדפתי לכתוב פחות קוד (להיות "פייתוני").
זה הכל לגבי map. עכשיו בואו נעבור ל-filter()
Filter
בזמן ש-map()
מעבירה כל אלמנט ברשימה דרך פונקציה ומחזירה את התוצאה של כל האלמנטים שעברו דרך הפונקציה, filter()
, קודם כל, דורשת שהפונקציה תחזיר ערכים בוליאניים (true או false) ואחר כך מעבירה כל אלמנט באוביקט דרך הפונקציה, "מסננת" את אלו שהם false. יש לה את התחביר הבא:
filter(func, iterable)
הנקודות הבאות צריכות לציון לגבי filter()
:
- בשונה מ-
map()
, נדרש רק אוסף אחד. - הפונקציה המועברת כארגומנט ל-
func
נדרשת להחזיר ערך בוליאני. אם היא לא עושה זאת, הפונקציהfilter
פשוט תחזיר את האוסף שהועבר אליה. כמו כן, מכיוון שנדרש רק אוסף אחד, זה משתמע ש-func
חייב לקבל רק ארגומנט אחד. 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
) של חשודים בפלינדרום.
# 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
.
זה הכל על 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!