ترتيب القوائم ودوالها المدمجة
الفرق بين sorted() و .sort()، والنتائج التي تقدمها max و min و sum و count و index، وتغيير معايير الترتيب باستخدام key=، وتحويل القائمة لنص قابل للقراءة بواسطة join().
- 1المشكلة
- 2الفهم
- 3أمثلة محلولة
- 4التوقع
- 5التطبيق
- 6التحدي
المشكلة التي نقوم بحلها
لدينا قائمة تحتوي على درجات سبعة طلاب. ونحتاج إلى إجابات لثلاثة أسئلة: ما هي أعلى درجة؟ ما هو المتوسط الحسابي؟ ومن هم أصحاب أعلى ثلاث درجات؟
باستخدام الأدوات التي تعلمناها في الدرس الثاني عشر، يمكننا الإجابة عن أول سؤالين باستخدام حلقة تكرارية — وهو أمر يتطلب بعض الجهد ولكنه ممكن تماماً. أما السؤال الثالث فمختلف تماماً؛ فمعرفة "أعلى ثلاث درجات" يتطلب ترتيب الدرجات أولاً، ونحن لا نملك حتى الآن طريقة لترتيب أي شيء في الكود.
تتكرر هذه المهام بكثرة في الحياة العملية لدرجة أن بايثون توفر أدوات جاهزة مدمجة للتعامل معها. يركز درس اليوم على هذه الحزمة من الأدوات — بالإضافة إلى فخ شهير يقع فيه الجميع تقريباً مرة واحدة على الأقل: توجد صيغتان مختلفتان للعملية نفسها، إحداهما تعدل القائمة الأصلية مباشرة، والأخرى تنشئ قائمة جديدة وتعيدها لك، واختيار الصيغة الخاطئة لن يظهر لك أي رسالة خطأ، بل سيترك البرنامج يتصرف بشكل غير متوقع في صمت!
في نهاية هذا الدرس ستكون قادراً على
- التمييز بين
sorted()و.sort()، ومعرفة متى تستخدم كلاً منهما - استخراج النتائج من القائمة باستخدام
maxوminوsumوcountوindex - تغيير معيار الترتيب وقواعده باستخدام وسيط
key= - تحويل القائمة إلى نص منظم وقابل للقراءة باستخدام
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() فلم تنشئ أي قائمة جديدة، بل قامت بترتيب القائمة الأصلية نفسها في مكانها، وضاع الترتيب القديم للأبد.
هنا تتجلى قاعدة الدرس الثاني عشر مرة أخرى: الدالة أو الطريقة التي تعدل القائمة الأصلية مباشرة لا تُرجع أي قيمة.
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.0الدوال max و min و sum هي دوال عامة مدمجة وليست دوال تابعة (methods)، لذا نكتب max(marks) وليس marks.max(). ولا توجد دالة مدمجة خاصة بالمتوسط الحسابي: فالمجموع مقسوماً على العدد الإجمالي هو المتوسط بعينه، وتأتي round(..., 2) لتقريب الناتج إلى منزلتين عشريتين للعرض.
لاحظ كيف اختُصرت حلقة التجميع التكرارية من الدرس العاشر في سطر واحد أنيق. ومع ذلك، لم يكن تعلم الحلقة مضيعة للوقت — فما تفعله 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، ترجع الفهرس 1 ولا تذكر الظهور الثاني على الإطلاق.
والبحث عن عنصر غير موجود يوقف البرنامج ويطلق خطأ:
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']عندما يرى البعض النتيجة الأولى يظنون أن بايثون أخطأت. لكنها لم تخطئ — فهي ترتب النصوص بناءً على الترميز الرقمي للأحرف (character codes)، وفي هذا الترميز تسبق جميع الحروف الكبيرة (Capital) نظيراتها الصغيرة (lowercase). لذلك قفز الاسم Bilal إلى المقدمة.
أما الوسيط key=str.lower فيخبر بايثون: أثناء عملية الترتيب والمقارنة، عاملي كل اسم وكأنه مكتوب بحروف صغيرة. لاحظ أن الأسماء نفسها لم تتغير — فالاسم Bilal لا يزال يطبع بحرفه الكبير B — فقط عملية المقارنة هي التي استخدمت صيغة الحروف الصغيرة.
يمكن أن يكون الوسيط 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'الترتيب يعني في جوهره مقارنة العناصر زوجاً بزوج، وقد رأينا في الدرس الثامن أنه لا يمكن مقارنة رقم بنص باستخدام معامل الأصغر من <. نص رسالة الخطأ هنا مطابق تماماً لما رأيناه سابقاً. يتكرر هذا الخطأ باستمرار عند قراءة القوائم من ملفات 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 هي موضوع الدرس الثامن عشر — اقرأها الآن ببساطة على أنها تعني: "طبق دالة str() على كل قيمة".
مثال متكامل
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 بعد ترتيبها السطر كُتب بصيغة 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).
Step 4 of 6 — Predict
Check your understanding
sorted() was used. What do the two lines print?
marks = [72, 45, 90]
ordered = sorted(marks)
print(ordered)
print(marks)- A[45, 72, 90] [72, 45, 90]
- B[45, 72, 90] [45, 72, 90]
- C[72, 45, 90] [45, 72, 90]
- D[45, 72, 90] None
The result of .sort() is stored back into marks. What is printed?
marks = [72, 45, 90]
marks = marks.sort()
print(marks)- ANone
- B[45, 72, 90]
- C[72, 45, 90]
- DAn `AttributeError`
An attempt to join a list of numbers with commas. What happens?
marks = [72, 45, 90]
print(", ".join(marks))- AA `TypeError` — `join` can only join text
- B72, 45, 90
- C724590
- D[72, 45, 90]
Answering needs an account
Sign in to check your answers
The questions are above, and working them out in your head is the part that matters. Sign in to see the answers, the explanations and the three-level hints.
دورك الآن
اكتب ملفاً باسم inventory.py يحتوي على قائمة تضم سبعة أسماء منتجات على الأقل وقائمة أخرى بأسعارها.
من قائمة الأسعار، اطبع:
- أعلى سعر، وأدنى سعر، والمجموع الإجمالي، والمتوسط الحسابي (مقرباً لمنزلتين عشريتين)
- قائمة جديدة مرتبة — ثم أثبت أن القائمة الأصلية لم تتغير
- أغلى ثلاثة منتجات، باستخدام شريحة اقتطاع واحدة
من قائمة الأسماء، اطبع:
- الأسماء بترتيب أبجدي، مع تجاهل حالة الأحرف (كبيرة أو صغيرة)
- الأسماء مرتبة حسب طول الاسم وعدد حروفه
- جميع الأسماء في سطر واحد مفصولة بفواصل، باستخدام دالة
join
ثم أجرِ هذه التجربة العملية: استدعِ .sort() على قائمة الأسعار، واطبع الأسطر المطلوبة في الجزء الأول مرة أخرى. ما هي الأرقام التي تغيرت وما هي الأرقام التي لم تتغير؟ اشرح لنفسك لماذا لم تكن الأرقام الثابتة لتتغير بأي حال — واحتفظ بهذا الاستنتاج الذهني في ذاكرتك، ولن تقع في فخ هذا الدرس أبداً.
Step 6 of 6
التحدي — the chapter quiz
عشرة أسئلة متدرجة من السهل إلى الصعب. الأسئلة الأخيرة صعبة عن قصد.
Sign in to take the quiz