ترتيب القوائم ودوالها المدمجة
الفرق بين sorted() و .sort()، والنتائج التي تقدمها max و min و sum و count و index، وتغيير معايير الترتيب باستخدام key=، وتحويل القائمة إلى سلسلة نصية مقروءة باستخدام join().
كيف تريد أن تتعلم؟
المشكلة التي نقوم بحلها
لدينا درجات سبعة أشخاص في قائمة (list). علينا الإجابة عن ثلاثة أسئلة: ما أعلى درجة، وما المتوسط، ومن هم الثلاثة الأوائل؟
بأدوات الفصل السابق يمكن الإجابة عن السؤالين الأولين بكتابة حلقة — عمل متعب لكنه ممكن. السؤال الثالث مختلف. “الثلاثة الأوائل” تعني أن نرتّب أولاً، ولا نملك حتى الآن أي طريقة للترتيب.
هذه المهام مطلوبة كثيراً لدرجة أن بايثون توفرها جاهزة. فصل اليوم هو هذه الأدوات — ومعها فخ يقع فيه الجميع تقريباً مرة واحدة على الأقل: للعملية نفسها شكلان، أحدهما يعدّل القائمة نفسها والآخر يعيد قائمة جديدة، واختيار الشكل الخاطئ لا يسبب أي خطأ.
في نهاية هذا الفصل ستكون قادراً على
- شرح الفرق بين
sorted()و.sort()، ومعرفة متى تحتاج كلاً منهما - استخراج الإجابات من القائمة باستخدام
maxوminوsumوcountوindex - تغيير قاعدة الترتيب باستخدام
key= - تحويل قائمة إلى سلسلة نصية (string) مقروءة باستخدام
join() - شرح سبب ظهور
TypeErrorعند ترتيب قائمة فيها أنواع مختلطة
المتطلبات السابقة: القوائم — قيم متعددة تحت اسم واحد.
sorted() و.sort() — الفكرة الأساسية في هذا الفصل
كلاهما يرتّب. الفرق في ما يعود به.
marks = [72, 45, 90, 61]
ordered = sorted(marks)
print(ordered)
print(marks)
marks.sort()
print(marks)[45, 61, 72, 90]
[72, 45, 90, 61]
[45, 61, 72, 90]اقرأ الأسطر الثلاثة بانتباه.
أنشأت sorted(marks) قائمة جديدة مرتبة وأعادتها. بقيت marks الأصلية كما كانت تماماً — والسطر الثاني هو الدليل.
لم تنشئ marks.sort() أي قائمة جديدة. بل رتّبت القائمة الأصلية نفسها، وضاع الترتيب القديم نهائياً.
قاعدة الفصل السابق تعود هنا: الدالة العضو (method) التي تعدّل القائمة لا تعيد شيئاً.
marks = [72, 45, 90, 61]
marks = marks.sort()
print(marks)Noneتماماً مثل append. إذا كتبت .sort() فلا تحتفظ بالنتيجة؛ وإذا كتبت sorted() ولم تحتفظ بالنتيجة، فلا فائدة من العمل كله.
أيهما ومتى؟ إذا لم تعد تحتاج إلى الترتيب الأصلي، فإن .sort() أبسط وأقل كلفة. إذا كانت القائمة الأصلية مستخدمة في مكان آخر، أو لم تكن متأكداً — فاستخدم sorted()، لأنها لا تأخذ شيئاً من أحد. عند الشك، sorted().
الترتيب العكسي والإجابات الرقمية
marks = [72, 45, 90, 61]
print(sorted(marks, reverse=True))
print(max(marks), min(marks), sum(marks))
print(round(sum(marks) / len(marks), 2))[90, 72, 61, 45]
90 45 268
67.0max وmin وsum — الثلاث دوال (functions) وليست دوالاً عضوة، لذلك نكتب max(marks) وليس marks.max(). لا يوجد شيء خاص للمتوسط: المجموع مقسوماً على العدد هو المتوسط، وround(..., 2) تقرّبه إلى منزلتين عشريتين للعرض.
لاحظ أن عمل الحلقة التي تجمع القيم في الفصل 10 أصبح هنا سطراً واحداً. لم يذهب تعلّم الحلقة سدى — ما تفعله sum هو تلك الحلقة بالضبط، وسيأتي يوم تحتاج فيه إلى جمع شيء لا توجد له دالة جاهزة في بايثون.
العدّ والبحث
marks = [72, 45, 90, 45, 61]
print(marks.count(45))
print(marks.index(45))
print(marks.index(90))2
1
2تخبرك count(x) بعدد مرات وجود x. وتخبرك index(x) بمكان وجوده أول مرة — حتى مع وجود 45 مرتين تعطي فهرس (index) الأولى، وهو 1، ولا تذكر الثانية.
وإذا بحثت عن شيء غير موجود، تتوقف index:
marks = [72, 45, 90]
print(marks.index(50))ValueError: 50 is not in listتماماً مثل remove. اعتد على التحقق أولاً بـ if 50 in marks:.
ترتيب السلاسل النصية، وkey=
names = ["rafi", "Bilal", "ahmed"]
print(sorted(names))
print(sorted(names, key=str.lower))['Bilal', 'ahmed', 'rafi']
['ahmed', 'Bilal', 'rafi']عند رؤية النتيجة الأولى يظن كثيرون أن بايثون أخطأت. لم تخطئ — إنها ترتّب حسب رقم رمز كل حرف، وهناك تأتي كل الحروف الكبيرة قبل كل الحروف الصغيرة. لذلك انتقل Bilal إلى المقدمة.
تقول key=str.lower: عند الترتيب، اعتبر كل اسم مكتوباً بحروف صغيرة. الأسماء نفسها لا تتغير — ما زال B الكبير موجوداً في Bilal في النتيجة المطبوعة — بل يُستخدم الشكل الصغير عند المقارنة فقط.
يمكن أن تكون key أي قاعدة:
words = ["bag", "pen", "notebook", "ink"]
print(sorted(words, key=len))['bag', 'pen', 'ink', 'notebook']ترتيب حسب الطول. لاحظ أن طول bag وpen وink هو ثلاثة لكل منها، وقد بقيت بالترتيب الذي كانت عليه — عند التساوي لا تغيّر بايثون الترتيب. اسم هذه الخاصية stable sort، وهي مفيدة عند الترتيب حسب أكثر من شرط.
لا يمكن ترتيب أنواع مختلطة
values = [3, "1", 2]
print(sorted(values))TypeError: '<' not supported between instances of 'str' and 'int'الترتيب يحتاج إلى مقارنة العناصر زوجاً زوجاً، ورأينا في الفصل 8 أنه لا يمكن مقارنة رقم بسلسلة نصية باستخدام <. لذلك الرسالة هي الرسالة نفسها حرفياً. يحدث هذا كثيراً مع القوائم المقروءة من ملف CSV — حيث تكون كل الأرقام في الحقيقة سلاسل نصية.
من قائمة إلى سلسلة نصية — join
عند طباعة القائمة مباشرة تظهر مع الأقواس المربعة وعلامات الاقتباس، وهذا ليس شكلاً يُعرض على الناس:
names = ["rafi", "ahmed", "bilal"]
print(", ".join(names))
print(" | ".join(names))rafi, ahmed, bilal
rafi | ahmed | bilalيبدو السطر معكوساً في المرة الأولى: علامة الربط تأتي أولاً، والقائمة داخل القوسين. طريقة التذكر — “اربط هذه العناصر بهذه العلامة”.
يوجد شرط واحد، وهو صارم: يجب أن تكون كل القيم سلاسل نصية.
marks = [72, 45, 90]
print(", ".join(marks))TypeError: sequence item 0: expected str instance, int foundلربط قائمة أرقام، حوّل كل رقم إلى سلسلة نصية أولاً:
marks = [72, 45, 90]
print(", ".join(str(m) for m in marks))72, 45, 90الجزء str(m) for m in marks جديد، وسيُشرح بالكامل في فصل لاحق. اقرأه الآن هكذا فقط: “لكل m في marks، اصنع str(m).” ثم تربط join تلك السلاسل النصية. المثال الكامل أدناه يستخدم السطر نفسه.
مثال كامل
report.py:
# A marks report: order them, summarise them, and show the top three
marks = [72, 45, 90, 61, 88, 45, 33]
ordered = sorted(marks, reverse=True)
print("Marks :", marks)
print("Ordered :", ordered)
print("Count :", len(marks))
print("Highest :", max(marks))
print("Lowest :", min(marks))
print("Total :", sum(marks))
print("Average :", round(sum(marks) / len(marks), 2))
print("How many 45s:", marks.count(45))
top_three = ordered[:3]
print("Top three:", top_three)
print("As text :", ", ".join(str(m) for m in top_three))
passed = []
for mark in marks:
if mark >= 40:
passed.append(mark)
print("Passed :", len(passed), "of", len(marks))Marks : [72, 45, 90, 61, 88, 45, 33]
Ordered : [90, 88, 72, 61, 45, 45, 33]
Count : 7
Highest : 90
Lowest : 33
Total : 434
Average : 62.0
How many 45s: 2
Top three: [90, 88, 72]
As text : 90, 88, 72
Passed : 6 of 7لاحظ قرارين في التصميم.
اخترنا sorted() وليس .sort() — لأن marks الأصلية تُطبع مرة أخرى في السطر الأول. لو كتبنا .sort() لظهر سطرا “Marks” و“Ordered” متطابقين، ولم يظهر أي خطأ. هذا هو الخطأ الصامت الذي ذكرناه في بداية هذا الفصل.
استخرجنا الثلاثة الأوائل بشريحة — ordered[:3]. لم نحتج إلى دالة خاصة لـ“الثلاثة الأوائل”: أول ثلاثة في قائمة مرتبة ترتيباً عكسياً هم الثلاثة الأوائل. بعد الترتيب تصبح إجابة كثير من الأسئلة شريحة بهذه الطريقة.
حالات الخطأ الشائعة وحلولها
أصبحت القائمة None بعد .sort() كُتب marks = marks.sort(). ترتّب .sort() القائمة نفسها وتعيد None — اكتب marks.sort() فقط. إذا أردت قائمة جديدة فاكتب ordered = sorted(marks).
تغيّرت القائمة الأصلية بعد الترتيب استُخدمت .sort()، وهي ترتّب القائمة الأصلية نفسها. للحفاظ على الأصلية استخدم sorted().
TypeError: '<' not supported between instances of 'str' and 'int' القائمة تخلط الأرقام والسلاسل النصية. نفّذ print(marks) وانظر أي القيم بين علامات اقتباس؛ إذا قُرئت من ملف فهي غالباً كلها سلاسل نصية، وتحتاج إلى int().
ValueError: 50 is not in list أُعطيت index() أو remove() قيمة غير موجودة في القائمة. تحقق أولاً بـ in.
TypeError: sequence item 0: expected str instance, int found أُعطيت join() قائمة أرقام. حوّل كل رقم بـ str().
بعد ترتيب الأسماء جاءت كل الأسماء التي تبدأ بحرف كبير أولاً هذا هو السلوك الطبيعي، وليس خطأ. للترتيب بالشكل الذي يتوقعه الناس استخدم sorted(names, key=str.lower).
الخطوة ٤ من ٦ — التوقع
تحقّق من فهمك
استُخدمت sorted(). ماذا يطبع السطران؟
marks = [72, 45, 90]
ordered = sorted(marks)
print(ordered)
print(marks)- A[45, 72, 90] [72, 45, 90]
- B[45, 72, 90] None
- C[45, 72, 90] [45, 72, 90]
- D[72, 45, 90] [45, 72, 90]
تم حفظ نتيجة .sort() مرة أخرى في marks. ماذا يُطبع؟
marks = [72, 45, 90]
marks = marks.sort()
print(marks)- Aخطأ `AttributeError`
- B[72, 45, 90]
- C[45, 72, 90]
- DNone
محاولة لربط قائمة أرقام بفواصل. ماذا يحدث؟
marks = [72, 45, 90]
print(", ".join(marks))- Aخطأ `TypeError` — لا تربط `join` إلا السلاسل النصية
- B724590
- C72, 45, 90
- D[72, 45, 90]
الإجابة تتطلب حساباً
سجّل الدخول للتحقق من إجاباتك
الأسئلة موجودة أعلاه، والتفكير في إجاباتها ذهنياً هو الجزء المهم. سجّل الدخول لرؤية الإجابات والتفسيرات والتلميحات ذات المستويات الثلاثة.
دورك الآن
اكتب ملفاً باسم inventory.py فيه قائمة بأسماء سبعة منتجات على الأقل، وقائمة بأسعارها.
من قائمة الأسعار اطبع:
- الأعلى والأدنى والمجموع والمتوسط (المتوسط حتى منزلتين عشريتين)
- قائمة جديدة مرتبة — ثم أثبت أن القائمة الأصلية لم تتغير
- أغلى ثلاثة منتجات، باستخدام شريحة
من قائمة الأسماء اطبع:
- الأسماء مرتبة أبجدياً، مع تجاهل الفرق بين الحروف الكبيرة والصغيرة
- الأسماء مرتبة حسب طولها
- كل الأسماء في سطر واحد، تفصل بينها فواصل — باستخدام
join
في النهاية قم بتجربة: نفّذ .sort() على قائمة الأسعار، ثم اطبع أسطر البند 1 مرة أخرى. أي رقم تغيّر، وأي رقم لم يتغير؟ اشرح لنفسك لماذا لم تتغير الأرقام التي بقيت كما هي — إذا تذكرت هذه الإجابة، فلن يوقعك فخ هذا الفصل مرة أخرى.
الخطوة ٦ من ٦
التحدي — اختبار الفصل
عشرة أسئلة متدرجة من السهل إلى الصعب. الأسئلة الأخيرة صعبة عن قصد.
سجّل الدخول لأداء الاختبار