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 لديها نفس العدد المحدد كمدخلات. قبل أن ننتقل إلى مثال، من المهم أن تلاحظ ما يلي:

  1. في بايثون 2، تُعيد الدالة map() قائمة. ولكن في بايثون 3، تُعيد الدالة كائن map object والذي هو كائن توليد. للحصول على النتيجة كقائمة، يمكن استدعاء الدالة المضمنة list() على كائن map. أي 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())، حيث تقوم دالة 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():

  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 في بايثون. جرب التمارين أدناه لمساعدتك في التأكيد على استيعابك لكل وظيفة.

تمرين

في هذا التمرين، ستستخدم كلًا من 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