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
مدمجة مع بايثون (في وحدة __builtins__
) ولا تحتاج للاستيراد. لكن دالة reduce
تحتاج للاستيراد لأنها توجد في وحدة functools
. دعنا نحصل على فهم أفضل لكيفية عملها جميعًا، بدءًا من map
.
Map
لدى دالة map()
في بايثون الصيغة التالية:
map(func, *iterables)
حيث تمثل func
الدالة التي سيتم تطبيقها على كل عنصر في iterables
(بقدر ما هم). هل لاحظت النجمة (*
) على iterables
؟ تعني أنه يمكن أن يكون هناك العديد من الأشياء القابلة للتكرار، طالما أن func
لديها نفس العدد المحدد كمدخلات. قبل أن ننتقل إلى مثال، من المهم أن تلاحظ ما يلي:
- في بايثون 2، تُعيد الدالة
map()
قائمة. ولكن في بايثون 3، تُعيد الدالة كائنmap object
والذي هو كائن توليد. للحصول على النتيجة كقائمة، يمكن استدعاء الدالة المضمنةlist()
على كائن map. أي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()
)، حيث تقوم دالة map بذلك لكل عنصر في قائمة 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
ليست بنفس الطول؟ لا تستطيع؟ جربها! قم بتغيير طول واحدة منها.
على دالتنا الخاصة 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()
, وبايثون بشكل عام! ببساطة استخدمت دالlambda
. هذا ليس ليقول أن استخدام طريقة تعريف الدالة القياسية (def function_name()
) غير مسموح أو مقبول، لا يزال كذلك. لقد فضلت ببساطة كتابة كود أقل (كن "Pythonic").
هذا كل ما يتعلق بـ 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 في بايثون. جرب التمارين أدناه لمساعدتك في التأكيد على استيعابك لكل وظيفة.
تمرين
في هذا التمرين، ستستخدم كلًا من 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!