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 יש את אותו מספר הנדרש כארגומנטים של קלט. לפני שנעבור לדוגמה, חשוב שתשימו לב לדברים הבאים:

  1. בפייתון 2, הפונקציה map() מחזירה רשימה. לעומת זאת, בפייתון 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)

שזה יוציא את ['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():

  1. בשונה מ-map(), נדרש רק אוסף אחד.
  2. הפונקציה המועברת כארגומנט ל-func נדרשת להחזיר ערך בוליאני. אם היא לא עושה זאת, הפונקציה filter פשוט תחזיר את האוסף שהועבר אליה. כמו כן, מכיוון שנדרש רק אוסף אחד, זה משתמע ש-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) של חשודים בפלינדרום.

# 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!

Previous Tutorial Next Tutorial Take the Test
Copyright © learnpython.org. Read our Terms of Use and Privacy Policy