القوائم، والقواميس، والمجموعات، وحلقات التكرار واختصارات الحاويات
متى تستخدم كل حاوية من حاويات بايثون الأربع، وحلقات التكرار والشروط، واختصارات الحاويات، وإحصاء الكلمات في تذاكر الدعم غير المنظمة لاستخراج أعلى خمس كلمات بنتائج مستقرة دائماً.
كيف تريد أن تتعلم؟
المشكلة التي نقوم بحلها
طلب مدير فريق الدعم الفني معرفة أكثر الموضوعات التي اشتكى منها العملاء في تذاكر هذا الأسبوع. كانت التذاكر عبارة عن نصوص عشوائية غير منظمة — بعضها كُتب بأحرف كبيرة بالكامل، وبعضها مليء بعلامات الترقيم، وبعضها يكرر نفس الكلمات مراراً. كتب أحد الزملاء سكريبت سريعاً في خمس دقائق وأرسل تقريراً: احتلت كلمة the المرتبة الأولى، تليها refund، ثم ظهرت Refund وrefund. ككلمتين منفصلتين تماماً!
وفي التشغيل الثاني، ظهرت مشكلة أخرى: أدى تغيير طفيف في ترتيب التذاكر الأصلية إلى تغير الكلمة الخامسة في قائمة الكلمات الخمس الأكثر تكراراً، على الرغم من أن عدد تكرار أي كلمة لم يتغير على الإطلاق! لم يستطع أحد ممن قرأوا التقرير معرفة أي النسختين هي الصحيحة.
الحل هنا لا يكمن في مكتبة خارجية خارقة، بل في إتقان استخدام حاويات البيانات الأساسية الأربع في بايثون — القوائم (lists)، والصفوف (tuples)، والقواميس (dictionaries)، والمجموعات (sets) — وتوظيف حلقات التكرار والشروط البرمجية بالشكل الصحيح. فجزء كبير من عمل مهندس الذكاء الاصطناعي اليومي يتمثل في هذا تحديداً: تنظيف النصوص، والعد، والفلترة، والترتيب، بطريقة تضمن الحصول على النتيجة ذاتها في كل مرة لنفس المدخلات.
بنهاية هذا الفصل ستكون قادراً على
- الاختيار بثقة بين القائمة، والصف، والقاموس، والمجموعة لكل مهمة برمجية، وتوضيح سبب الاختيار
- كتابة حلقات تكرار نظيفة باستخدام
forوwhileوif/elif/elseوenumerateو.items() - فلترة البيانات وتحويلها ببراعة باستخدام اختصارات الحاويات (comprehensions) للقوائم والقواميس والمجموعات
- استخراج الكلمات من النصوص غير المنظمة وعدها باستخدام القاموس العادي وكائن
Counter - ترتيب الكلمات المتساوية في عدد التكرارات وفق قاعدة صريحة ومحددة، لضمان استقرار وتطابق النتائج دائماً
المتطلبات المسبقة: pathlib وإعدادات JSON وYAML ومتغيرات البيئة — والقدرة على تشغيل السكريبتات عبر uv run.
أربع حاويات لأربع مهام مختلفة
لنقارن أولاً بين الحاويات الأربع جنباً إلى جنب:
tickets = ["refund", "login", "refund", "billing"]
latest = ("T-1042", "refund", 3)
owners = {"refund": "Maya", "login": "Omar"}
seen = set(tickets)
print(tickets[0], tickets[-1], len(tickets))
print(latest[1])
print(owners["login"])
print(sorted(seen))
print("login" in seen, "shipping" in seen)refund billing 4
refund
Omar
['billing', 'login', 'refund']
True Falseتقدم كل حاوية منها ميزة وضمانة برمجية محددة، وعليك الاختيار بناءً على طبيعة تلك الضمانة:
- القائمة - List (
[...]) — تحافظ على ترتيب العناصر، وتسمح بتكرار نفس القيمة، وقابلة للتعديل والإضافة والحذف. تناسب "سلسلة من العناصر المتتالية": قائمة تذاكر، أو دفعة من مطالبات النماذج (prompts). - الصف - Tuple (
(...)) — يحافظ على الترتيب لكنه غير قابل للتعديل بعد إنشائه. يناسب مجموعة قيم ثابتة تشكل معاً سجلاً واحداً، مثل("T-1042", "refund", 3)— المعرف، والتصنيف، ومستوى الأولوية. فالموقع هنا يحمل معنى دلالياً محدداً. - القاموس - Dictionary (
{key: value}) — للبحث عن قيمة بدلالة مفتاح فريد. "من هو الموظف المسؤول عن كل تصنيف"، أو "كم مرة تكررت كل كلمة" — أي حالة تنتقل فيها من عنصر إلى معلومة مقابلة له. - المجموعة - Set (
set(...)أو{a, b}) — تحتفظ بكل قيمة مرة واحدة فقط بدون تكرار، ولا تضمن ترتيب العناصر. تناسب الحالات التي تسأل فيها فقط: "هل العنصر موجود؟" أو "كم عدد العناصر الفريدة؟".
استخدمنا sorted(seen) في الطباعة لأن ترتيب عناصر المجموعة عند طباعتها غير مضمون — فقد تظهر بترتيب معين في تشغيل وتختلف في التشغيل الذي يليه. وإذا اعتمدت مخرجات برنامجك على ترتيب مجموعة عشوائي، فلن تكون مخرجاتك مستقرة أبداً.
فحص الوجودinفي القائمة يتطلب مراجعة كل عنصر واحداً تلو الآخر؛ بينما في المجموعة أو القاموس يُجاب عنه فوراً، سواء كانت الحاوية تضم عشرة عناصر أو عشرة ملايين عنصر! فإذا كنت تفحص تكراراً شرط مثلif word in stop_words، يجب أن تكونstop_wordsمجموعة وليست قائمة. وسنتعرف على أسباب ذلك وكيفية قياسه بدقة لاحقاً في الدورة.
حلقات التكرار: حدد بوضوح ما تدور حوله
تكون حلقات التكرار حول الصفوف والقواميس أكثر وضوحاً عند فك حزم القيم (unpacking) مباشرة داخل متغيرات ذات أسماء معبرة:
ticket_id, category, priority = ("T-1042", "refund", 3)
print(ticket_id, category, priority)
owners = {"refund": "Maya", "login": "Omar"}
for category, owner in owners.items():
print(f"{category} -> {owner}")
for position, category in enumerate(["refund", "login"], start=1):
print(position, category)T-1042 refund 3
refund -> Maya
login -> Omar
1 refund
2 loginتعيد الدالة .items() زوجاً من (المفتاح, القيمة) في كل دورة، وتمنحك enumerate العنصر مع رقم موقعه — دون الحاجة لعد المؤشرات يدوياً بـ range(len(...)). ومنذ إصدار بايثون 3.7، يتذكر القاموس ترتيب إدخال مفاتيحه، لذا تبدأ حلقة التكرار حول owners بالمفتاح refund.
ماذا يحدث لو نسيت كتابة .items()؟
owners = {"refund": "Maya", "login": "Omar"}
for category, owner in owners:
print(category, owner)Traceback (most recent call last):
File "main.py", line 2, in <module>
for category, owner in owners:
^^^^^^^^^^^^^^^
ValueError: too many values to unpack (expected 2)التكرار المباشر حول القاموس يمر عبر المفاتيح فقط. المفتاح الأول "refund" عبارة عن نص من ستة أحرف، ويحاول بايثون فك حزمته في متغيرين اثنين فقط — ستة أحرف مقابل فراغين اثنين! قد تبدو رسالة الخطأ غريبة في البداية حتى تتذكر أن النص في بايثون هو سلسلة متتابعة أيضاً.
تتوقف سلسلة شروط if/elif/else عند أول شرط يتحقق، ولذا فإن ترتيب الشروط يمثل المنطق البرمجي بحد ذاته:
latencies_ms = [120, 340, 95, 1800, 210, 2600]
for latency in latencies_ms:
if latency >= 2000:
label = "critical"
elif latency >= 1000:
label = "slow"
else:
label = "ok"
print(latency, label)120 ok
340 ok
95 ok
1800 slow
210 ok
2600 criticalإذا عكست ترتيب الشروط — فوضعت>= 1000أولاً، ثم>= 2000ثانياً — فلن يظهر أي خطأ برمجي، لكن القيمة2600ستصنف خطأً على أنهاslow، لأن الشرط الأول تحقق وتوقفت السلسلة عنده. ولن يتم تنفيذ فرعcriticalأبداً. فعند التقسيم بحسب الحدود الرقمية، ضع الشرط الأكثر صرامة في البداية دائماً.
تُستخدم حلقة for للتكرار على مجموعة معلومة ومحددة. بينما تُستخدم while طالما ظل الشرط محققاً — حينما لا تعلم مسبقاً عدد الدورات المطلوبة. وجلب البيانات من واجهة برمجية صفحة تلو الأخرى هو المثال الكلاسيكي لذلك. في هذا المثال، يلعب القاموس دور الواجهة البرمجية:
pages = {1: ["t1", "t2"], 2: ["t3"], 3: []}
page = 1
collected = []
while True:
batch = pages[page]
if not batch:
break
collected.extend(batch)
page += 1
print(collected, "pages read:", page - 1)['t1', 't2', 't3'] pages read: 2الصفحة الفارغة تعني استدعاء break للتوقف. يعمل الشرط if not batch لأن القائمة الفارغة تعتبر خاطئة (falsy) في بايثون. في مثل هذه الحلقات، اكتب شرط التوقف في البداية أولاً، ثم أكمل بقية الكود — فحلقة while True التي تنسى فيها شرط التوقف ستدور إلى ما لا نهاية دون إظهار أي خطأ.
اختصارات الحاويات (Comprehensions): بناء حاوية من أخرى
نمط "قائمة فارغة، ثم حلقة تكرار، ثم شرط if، ثم append" — نمط شهير جداً يتكرر في أربعة أسطر لدرجة أن بايثون يقدم صيغة مختصرة وأنيقة لكتابته في سطر واحد:
latencies_ms = [120, 340, 95, 1800, 210, 2600]
slow = [ms for ms in latencies_ms if ms >= 1000]
seconds = [ms / 1000 for ms in latencies_ms]
labels = {ms: ("slow" if ms >= 1000 else "ok") for ms in latencies_ms}
buckets = {ms // 1000 for ms in latencies_ms}
print(slow)
print(seconds)
print(labels)
print(sorted(buckets))[1800, 2600]
[0.12, 0.34, 0.095, 1.8, 0.21, 2.6]
{120: 'ok', 340: 'ok', 95: 'ok', 1800: 'slow', 210: 'ok', 2600: 'slow'}
[0, 1, 2]طريقة قراءتها: [النتيجة لكل عنصر في الحاوية إذا تحقق الشرط]. يحدد الجزء الأيسر ما سيتم بناؤه من كل عنصر؛ بينما يحدد شرط if اللاحق أي العناصر يتم الاحتفاظ بها. والشكل نفسه مع {المفتاح: القيمة for ...} يبني قاموساً، ومع {القيمة for ...} يبني مجموعة — تضم buckets ثلاث فئات زمنية فريدة مدة كل منها ثانية، دون أي تكرار.
لا تخلط بين نوعي شرط if. فالشرط اللاحق if ms >= 1000 هو فلتر (filter) — إذا كان خاطئاً، يُستبعد العنصر تماماً. أما التعبير "slow" if ms >= 1000 else "ok" فهو تحديد قيمة شرطي (ternary choice) — يبقى كل عنصر في الحاوية، وتكون قيمته واحدة من الاثنتين. ولهذا يحتوي القاموس labels على جميع العناصر الستة.
تُستخدم الاختصارات لبناء حاوية جديدة حصراً. أما إذا كنت تريد فقط طباعة شيء أو الكتابة في ملف — حيث لا توجد حاوية جديدة ستُحفظ — فاكتب حلقة for عادية. واختصار متداخل من ثلاثة مستويات قد يُكتب في سطر واحد، لكن حلقة عادية من خمسة أسطر تكون في الغالب أسهل بكثير في القراءة والمراجعة لزملائك.
عد الكلمات: يدوياً، ثم باستخدام Counter
محاولة الجميع الأولى تقريباً لعد الكلمات باستخدام القاموس تبدو هكذا:
words = ["refund", "late", "refund", "login", "refund", "late"]
counts = {}
for word in words:
counts[word] += 1Traceback (most recent call last):
File "main.py", line 5, in <module>
counts[word] += 1
~~~~~~^^^^^^
KeyError: 'refund'العملية counts[word] += 1 تعني برمجياً counts[word] = counts[word] + 1 — وعند محاولة قراءة counts[word] في الطرف الأيمن، يجد بايثون أن المفتاح "refund" غير موجود في القاموس بعد! الكلمة التي تظهر لأول مرة تحتاج لقيمة ابتدائية. وتوفر الدالة .get(key, 0) هذا الحل بدقة:
words = ["refund", "late", "refund", "login", "refund", "late"]
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts){'refund': 3, 'late': 2, 'login': 1}هذه المهمة شائعة جداً لدرجة أن مكتبة بايثون القياسية توفر فئة مخصصة لها — collections.Counter، وهي نوع خاص من القواميس:
from collections import Counter
words = ["refund", "late", "login", "refund", "login", "late", "slow"]
counts = Counter(words)
print(counts)
print(counts.most_common(2))
print(counts["missing"])Counter({'refund': 2, 'late': 2, 'login': 2, 'slow': 1})
[('refund', 2), ('late', 2)]
0محاولة قراءة مفتاح مفقود من كائن Counter تعيد القيمة 0 بدلاً من إطلاق خطأ KeyError — وهو السلوك المثالي لعمليات الإحصاء. لكن ألقِ نظرة على نتيجة most_common(2): الكلمات refund وlate وlogin تمتلك جميعها تكراراً مساوياً لـ 2، ومع ذلك أُعيدت أول كلمتين فقط. لماذا استُبعدت login؟ لأن most_common تحتفظ عند تساوي التكرارات بترتيب أول ظهور للكلمات في المدخلات. ظهرت كلمة login ثالثاً في القائمة، فخسرت الترتيب. إذا غيرت ترتيب التذاكر المدخلة ستتغير النتيجة أيضاً — وهذا هو اللغز الغامض الذي واجه زميلنا في بداية الفصل!
تنظيف النصوص: أي الكلمات تُعد كلمات حقيقية؟
تقوم دالة split() بالتقطيع عند المسافات البيضاء فقط:
text = "Refund not received. REFUND please! Login fails; login page is slow."
print(text.split())
print(text.lower().split())['Refund', 'not', 'received.', 'REFUND', 'please!', 'Login', 'fails;', 'login', 'page', 'is', 'slow.']
['refund', 'not', 'received.', 'refund', 'please!', 'login', 'fails;', 'login', 'page', 'is', 'slow.']عالجت دالة .lower() اختلاف حالة الأحرف الكبيرة والصغيرة، لكن كلمات مثل received. وplease! وslow. لا تزال ملتصقة بعلامات الترقيم — وعند العد، تُعامل slow. وslow ككلمتين مختلفتين تماماً! هنا تبرز قوة التعابير النمطية (Regular Expressions): فبدلاً من محاولة تحديد ما تريد حذفه، تحدد بدقة كيف يبدو شكل الكلمة المطلوبة:
import re
text = "Refund not received. REFUND please! Login fails; login page is slow. Can't pay."
words = re.findall(r"[a-z']+", text.lower())
print(words)['refund', 'not', 'received', 'refund', 'please', 'login', 'fails', 'login', 'page', 'is', 'slow', "can't", 'pay']النمط [a-z']+ يعني "حرف إنجليزي صغير أو فاصلة عليا (apostrophe) واحدة أو أكثر". تعيد re.findall كل المواضع التي تطابق هذا النمط في النص، ويتم تجاهل كل ما عداها — النقاط، والفواصل، والأرقام. وتبقى كلمة can't كلمة واحدة سليمة.
يعاني هذا النمط من ثغرة بسيطة: إذا كتب أحدهم نصاً بين علامات تنصيص مفردة مثل'invalid token'، فستستخرج الكلمة الأولى كـ'invalidملتصقة بعلامة التنصيص. سنحكم النمط في المثال التالي بحيث لا تظهر الفاصلة العليا إلا بين حرفين فقط. وتذكر أن النمط[a-z]مخصص للأحرف الإنجليزية فقط؛ والنصوص المكتوبة بلغات أخرى كالعربية تحتاج نمطاً مختلفاً.
تساوي التكرارات: حدد قاعدة الترتيب بنفسك
لتحويل الإحصاءات إلى قائمة مرتبة، يتعين عليك إجراء عملية ترتيب (sorting). إليك ثلاث طرق لتطبيق ذلك على قاموس تتساوى فيه كلمتا refund وlogin بـ 3 تكرارات:
counts = {"refund": 3, "late": 2, "login": 3, "slow": 1}
print(sorted(counts, reverse=True))
print(sorted(counts.items(), key=lambda item: item[1], reverse=True))
print(sorted(counts.items(), key=lambda item: (-item[1], item[0])))['slow', 'refund', 'login', 'late']
[('refund', 3), ('login', 3), ('late', 2), ('slow', 1)]
[('login', 3), ('refund', 3), ('late', 2), ('slow', 1)]السطر الأول هو الأخطر، لأنه يبدو منطقياً للوهلة الأولى! ترتيب القاموس مباشرة يرتب مفاتيحه وليس قيمه — وهذا يعني ترتيباً هجائياً عكسياً لا علاقة له بعدد التكرارات إطلاقاً. ظهرت كلمة slow مرة واحدة فقط، ومع ذلك تصدرت القائمة! دون أن يصدر أي خطأ.
السطر الثاني يرتب حسب التكرارات. لكن أيهما تأتي أولاً: refund أم login؟ دالة sorted في بايثون "مستقرة (stable)" — أي أن العناصر المتساوية تحتفظ بترتيب ورودها الأصلي في المدخلات. أُدخلت كلمة refund في القاموس أولاً، فظهرت أولاً. وبعبارة أخرى: تعتمد النتيجة هنا على ترتيب وصول البيانات.
أما السطر الثالث، فهو يعلن عن القاعدة بوضوح تام. تعيد دالة key صفاً (tuple)، وتتم مقارنة الصفوف عنصراً تلو الآخر: أولاً -item[1] — القيمة السالبة للتكرار، لتظهر التكرارات الأكبر أولاً — وعند التساوي، يُقارن item[0]، أي الكلمة نفسها هجائياً. الآن ستكون النتيجة متطابقة تماماً مهما اختلف ترتيب وصول البيانات في المدخلات. ولا حاجة لاستخدام reverse=True، لأن إشارة السالب عكست ترتيب التكرارات بالفعل مع الإبقاء على الترتيب الهجائي طبيعياً.
في أي مكان تُقارن فيه النتائج — كالتقارير، والاختبارات، وتقييمات الذكاء الاصطناعي — ضع قاعدة صريحة لحسم التساوي. وإلا فسيختلف تقرير اليوم عن تقرير الغد في حين أن البيانات الحقيقية لم تتغير على الإطلاق.
مثال عملي كامل
لنجمع الآن كل هذه الأجزاء معاً: استخراج الكلمات الخمس الأكثر تكراراً في تذاكر الدعم. نقوم باستبعاد كلمات التوقف الشائعة (Stop Words) — مثل the وand التي تملأ كل نص دون أن تضيف معنى مفيداً — ونستبعد الكلمات المكونة من حرفين أو أقل، ونحسم حالات التساوي هجائياً. ملف top_words.py:
import re
from collections import Counter
STOP_WORDS = {
"a", "and", "for", "i", "is", "it", "my", "not", "of", "on", "the", "to", "was", "with",
}
TICKETS = [
"Refund not received for order 1182. I was charged twice!",
"The app is slow and the login page freezes.",
"Charged twice again. Please refund the second charge.",
"Login fails with 'invalid token' on my phone.",
"Still waiting on my refund, it was promised last week.",
"App slow after the update; login takes a minute.",
]
def tokenize(text: str) -> list[str]:
return re.findall(r"[a-z]+(?:'[a-z]+)?", text.lower())
def top_words(texts: list[str], n: int = 5) -> list[tuple[str, int]]:
counts = Counter(
word
for text in texts
for word in tokenize(text)
if word not in STOP_WORDS and len(word) > 2
)
ranked = sorted(counts.items(), key=lambda item: (-item[1], item[0]))
return ranked[:n]
def print_ranking(ranking: list[tuple[str, int]]) -> None:
if not ranking:
print("no words to rank")
return
width = max(len(word) for word, _ in ranking)
for rank, (word, count) in enumerate(ranking, start=1):
print(f"{rank}. {word:<{width}} {count} {'#' * count}")
if __name__ == "__main__":
print_ranking(top_words(TICKETS))
print_ranking(top_words([]))uv run top_words.py1. login 3 ###
2. refund 3 ###
3. app 2 ##
4. charged 2 ##
5. slow 2 ##
no words to rankالقرارات الهندسية في هذا الكود:
نمط استخراج الكلمات [a-z]+(?:'[a-z]+)?. حرف أو أكثر، متبوعة اختيارياً بفاصلة عليا ومزيد من الأحرف. تظل كلمة can't كلمة واحدة، بينما علامات التنصيص المحيطة بـ 'invalid token' لن تلتصق بالكلمات أبداً. التعبير (?:...) يمثل مجموعة غير ملتقطة (non-capturing group) — فبدون ?: كانت re.findall ستعيد محتوى القوس فقط بدلاً من الكلمة كاملة.
استخدام مجموعة لحفظ كلمات التوقف. يتم فحص كل كلمة عبر word not in STOP_WORDS؛ وهذا الفحص شبه فوري في المجموعات مهما كبر حجم القائمة.
تغذية Counter مباشرة عبر تعبير توليدي (generator). عبارتا for مع شرط if تشكل تدفقاً بيانياً واحداً — لكل كلمة في كل تذكرة تجتاز الفلتر، دون الحاجة لبناء قائمة وسيطة ضخمة تستهلك الذاكرة. ويكون ترتيب عبارتي for مطابقاً لترتيب الحلقات المتداخلة العادية: الحلقة الخارجية أولاً.
حسم التعادل بقاعدة معلنة. حصلت كل من login وrefund على 3 تكرارات؛ وحصلت app وcharged وslow وtwice على تكرارين. حسم الترتيب الهجائي من يحصل على المركز الخامس، فاستُبعدت كلمة twice. القاعدة ليست مثالية بالضرورة — فلم تُستبعد twice لأنها أقل أهمية — ولكنها قاعدة معلنة وثابتة في كل تشغيل. وإذا سأل من يقرأ التقرير عن السبب، فهناك تفسير برمجي واضح.
التعامل الواعي مع المدخلات الفارغة. استدعاء max() على حاوية فارغة يرمي خطأ ValueError: max() iterable argument is empty. مرور أسبوع هادئ دون أي تذاكر هو أمر طبيعي وليس خطأ برمجياً — لذا تفحص print_ranking الحاوية أولاً. ويظهر السطر الأخير في المخرجات معالجة هذه الحالة بدقة.
فصل الحساب عن العرض. تعيد دالة top_words قائمة بالنتائج ولا تطبع أي شيء؛ بينما تتولى print_ranking الطباعة فقط. وبهذا يمكن استخدام نفس دالة الإحصاء في اختبار آلي، أو واجهة API، أو لوحة تحكم. التنسيق {word:<{width}} يحاذي الكلمات يساراً بعرض أطول كلمة، مما يجعل عمود الأرقام مصطفاً بشكل متناسق.
هناك قيد بسيط: عوملت charged وcharge ككلمتين منفصلتين تماماً هنا. توحيد صيغ الكلمات (lemmatization) هو علم قائم بذاته، وغالباً ما تطابق أنظمة الذكاء الاصطناعي النصوص وفق المعنى الدلالي (semantic meaning) بدلاً من مجرد عد الكلمات — وسنتعلم ذلك لاحقاً في المسار. ولكن حتى مع النماذج المتقدمة، تظل مهارات هذا الفصل — تنظيف النصوص وفلترتها وترتيبها بثبات — هي الخطوة الأساسية الأولى دائماً.
عند حدوث مشاكل شائعة
KeyError: 'refund' محاولة زيادة عداد counts[word] += 1 لمفتاح غير موجود بعد في القاموس. اكتب counts[word] = counts.get(word, 0) + 1، أو استخدم collections.Counter.
ValueError: too many values to unpack (expected 2) استخدام for key, value in d: مباشرة على قاموس. يعيد التكرار المباشر المفاتيح فقط؛ اكتب d.items() للحصول على الأزواج.
TypeError: unhashable type: 'list' محاولة وضع قائمة داخل مجموعة أو استخدامها كمفتاح في قاموس. يجب أن تكون عناصر المجموعات ومفاتيح القواميس غير قابلة للتعديل (immutable). استخدم الصف (tuple) بدلاً من القائمة: seen.add(("refund", "late")).
RuntimeError: dictionary changed size during iteration حذف أو إضافة مفاتيح إلى القاموس أثناء التكرار حوله في حلقة. ابنِ قاموساً جديداً بدلاً من التعديل أثناء الدوران: counts = {word: n for word, n in counts.items() if n >= 2}.
AttributeError: 'tuple' object has no attribute 'append' الصف (tuple) غير قابل للتعديل بعد إنشائه. إذا كانت البيانات تزيد وتنقص، استخدم قائمة؛ وإذا كان الصف يمثل سجلاً ثابتاً، فابنِ صفاً جديداً بالقيمة المحدثة.
ValueError: max() iterable argument is empty استدعاء max() أو min() على حاوية فارغة. تحقق من عدم فراغها أولاً، أو حدد قيمة افتراضية: max(lengths, default=0).
الخطوة ٤ من ٦ — التوقع
تحقّق من فهمك
A colleague is taking "the most frequent word" from a set of counts. What prints?
counts = {"refund": 5, "slow": 1, "login": 3}
print(sorted(counts, reverse=True)[0])- A`refund`
- B`('refund', 5)`
- C`slow`
- D`5`
label(2600) should return "critical", but it returns "slow" — with no error. Where is the bug?
def label(latency_ms):
if latency_ms >= 1000:
return "slow"
elif latency_ms >= 2000:
return "critical"
return "ok"
print(label(2600))- AThe order of the conditions: `>= 1000` is true first and the chain stops, so `>= 2000` is never reached — the strictest condition must come first
- B`return "ok"` needs an `else:` before it
- CThe second condition must use `if` instead of `elif`
- D`2600` is an integer, so it must be converted with `float()` before comparing with `>=`
A pipeline checks, for each of a million words, whether it is one of 300 stop words. Which container should hold the stop words?
- AA string, with all the words joined into one long text
- BA tuple, because stop words do not change
- CA set, because an `in` check is near-instant whatever its size
- DA list, because it can be kept in alphabetical order
اللعب جزء من Pro
+30 XP بانتظارك
العب هذا الفصل مع Pro
القراءة مجانية، والفصل كله متاح. أول 5 فصول من كل دورة مجانية متاحة للعب مجانًا. ابتداءً من الفصل 6، الإجابة عن الأسئلة وكسب نقاط XP وتنمية سلسلتك جزء من Pro.
افتح اللعب مع Proدورك الآن
استخرج الآن الكلمات الأكثر تكراراً لكل تصنيف على حدة. في ملف باسم categories.py، انسخ STOP_WORDS ودالة tokenize المشروحتين في هذا الفصل، وأضف قائمة التذاكر التالية:
TICKETS = [
("billing", "Charged twice for one order."),
("billing", "Refund for the double charge, please."),
("login", "Login fails on the new phone."),
("billing", "Still no refund after the charge."),
("login", "Password reset email never arrives."),
("login", "Login page shows a blank screen."),
]ثم اكتب كوداً يحقق المتطلبات التالية:
- يمتلك كل تصنيف إحصاءاته الخاصة — قاموس تكون مفاتيحه هي أسماء التصنيفات وقيمه هي كائنات
Counterالخاصة بكل تصنيف (تلميح:dict.setdefaultوCounter.update) - يتم استبعاد كلمات التوقف (stop words) والكلمات المكونة من حرفين أو أقل من العد
- يعرض كل تصنيف أعلى كلمتين تكراراً، مع تقديم التكرار الأعلى أولاً والترتيب هجائياً عند التساوي
- تُطبع التصنيفات بترتيب هجائي، مهما كان ترتيب ورود التذاكر الأصلية
يجب أن ينتج عن تشغيل uv run categories.py هذه المخرجات تحديداً:
billing charge=2, refund=2
login login=2, arrives=1بعد ذلك اعكس ترتيب قائمة التذاكر (TICKETS[::-1]) وشغل الكود مجدداً — هل تغيرت المخرجات على الإطلاق؟ إذا تغيرت، فما هو الشرط الذي لم يتم تطبيقه؟ وأخيراً، احذف قاعدة حسم التعادل ورتب حسب التكرار فقط: ما هي الكلمة الثانية لتصنيف login، وهل ظلت ثابتة عند عكس ترتيب التذاكر؟
الخطوة ٦ من ٦
التحدي — اختبار الفصل
عشرة أسئلة متدرجة من السهل إلى الصعب. الأسئلة الأخيرة صعبة عن قصد.
العب مع Pro