ياسمين معلمة حاسوب في مدرسة بأسيوط، وأول برنامج كتبته بعد الدورة يقارن كشف حضور اليوم بسجل الطلاب ويطبع من غاب. السجل 3,200 طالب، والكشف 240 سطرا. الجواب صحيح، والزمن 0.004 ثانية.
في آخر السنة شغلت البرنامج نفسه على ملف السنة كلها: 180 يوما دراسيا، 576,000 سطر. الجواب صحيح كما كان. الزمن 8.7 ثانية.
لم يتغير في البرنامج حرف واحد. السطر الذي دفع الفرق هو هذا:
missing = [r for r in today if r not in ids]
سطر واحد بثمنين، ولا شيء في صياغته يقول أيهما
القيمة ids قائمة. البحث في قائمة يمر على عناصرها، والبحث في مجموعة لا يمر، وهذا هو الفرق كله. كل سطر في الكشف يدفع المفسر إلى المشي في السجل حتى يجد الرقم أو ينتهي السجل، أي إلى 3,200 خطوة في أسوأ الأحوال. 240 سطرا تشتري مشيا لا يشعر به أحد. 576,000 سطرا تشتري المشي نفسه مكررا، فيظهر.
الإصلاح سطر واحد يسبق الحلقة:
ids = set(ids)
الزمن على ملف السنة نزل من 8.7 ثانية إلى 0.01 ثانية. المجموعة لا تمشي: تحسب من الرقم نفسه موضعا وتنظر في ذلك الموضع وحده، فيكلف البحث الواحد الثمن نفسه عند 3,200 عنصر وعند ملايين. قيست الأزمنة الثلاثة في سبتمبر 2026 على حاسوب محمول بمعالج ARM وPython 3.14، والقياس يعاد في سطرين على أي جهاز، وذلك هو المقصود منه.
وهذا كل ما تعنيه كلمة الأساسيات هنا. ليست مقررا يحفظ قبل الكتابة. هي معرفة أي سطر يتغير سعره حين يكبر الملف، ولا تثبت هذه المعرفة إلا على رقم رئي بالعين.
حلقة داخل حلقة تضرب الثمن في نفسه
ثم سألت ياسمين سؤالا ثانيا عن الملف نفسه: أي سطر تكرر؟ والجواب المباشر حلقة داخل حلقة.
dupes = [r for i, r in enumerate(rows) for s in rows[i+1:] if r == s]
حلقة داخل حلقة على القائمة نفسها تكلف n في n. على كشف يوم واحد هذا 240 في 240، أي 57,600 مقارنة، ولا يلاحظ زمنه. على ملف السنة هو 576,000 في 576,000، أي أكثر من 331 مليار مقارنة. ملف السنة أكبر 2400 ضعفا من كشف اليوم، وثمنه بهذا السطر أغلى بمربع ذلك العدد لا بالعدد نفسه، وهنا يفترق ما يعمل عما لا ينتهي.
والجواب نفسه بمرور واحد:
from collections import Counterdupes = [r for r, n in Counter(rows).items() if n > 1]
القاموس هنا اشترى الزمن بذاكرة: مرور واحد يبني عدادا، بدل مرور داخل مرور لا يبني شيئا. هذه المقايضة تتكرر تحت كل عنوان في هذه الصفحة، وهي مقايضة تختار عن قصد لا مقايضة تكتشف بعد شهر.
أربعة أشكال، وكل واحد منها رهان على السؤال الذي ستكرره
القاموس يجيب عن مفتاح، والمجموعة عن وجود، والقائمة عن ترتيب، والصف عن شيء لن يتغير بعد إنشائه. اختيار الشكل ليس ذوقا في التسمية، هو إعلان مبكر عن السؤال الذي سيتكرر آلاف المرات داخل البرنامج.
- القائمة
list: ترتيب وتكرار مسموح، والبحث فيها مشي من أولها. - المجموعة
set: وجود بلا تكرار وبلا ترتيب، والبحث فيها نداء واحد. - القاموس
dict: مفتاح يقابله شيء، بثمن النداء نفسه. - الصف
tuple: قائمة لا تعدل بعد الإنشاء، ولذلك تصلح مفتاحا للقاموس.
ولهذا الاختيار وجه ثان لا يظهر يوم الاختيار. القائمة الممررة إلى اسمين قائمة واحدة باسمين، فالسطر التالي لا ينسخ شيئا:
absent = missingabsent.append("ST-40311")
وبعد السطرين طول missing زاد أيضا، لأنه هو نفسه. النسخة تطلب صراحة، بكتابة missing[:] أو list(missing). هذا العطل يظهر عادة بعد أسابيع وفي دالة أخرى غير التي فيها السطر، وهو من أكثر ما يحول صفحة تصحيح الأخطاء من مهارة كمالية إلى شرط للعمل.
الترتيب الذي يعمل يبدأ بقياس لا بمصطلح
الترتيب الشائع في المادة العربية هو ترتيب المقرر الجامعي: تعريف التعقيد الزمني، ثم رموز O الكبرى، ثم فهرس الهياكل، ثم الخوارزميات المسماة. وهو ترتيب يعطي القارئ قدرة على الإجابة في امتحان، ولا يعطيه قدرة على اختيار سطر.
الترتيب المعكوس أرخص. قياس واحد على ملف يملكه فعلا، ثم أثمان الأشكال، ثم مقابلة المسألة مرة ثانية حتى يصير النمط جوابا عن عطل رآه بنفسه، ثم الكتابة لمن سيفتح الملف بعده. من بدأ من الرمز يحفظ أن البحث الثنائي لوغاريتمي؛ ومن بدأ من الرقم يعرف متى يستحق فرز القائمة ثمنه أصلا.
والخوارزميات موضعها بعد ذلك لا قبله، لأن الخوارزمية المسماة جواب، ومن لم يقع على السؤال يحفظ الجواب ولا يتعرف عليه حين يمر به. والقاعدة نفسها تصح على أنماط التصميم البرمجي، وثمن مخالفتها هناك أعلى، لأن النمط المحفوظ مبكرا يطبق على مسألة لا تشبهه.
الترتيب
كل درجة هي ما يجعل التي فوقها تعني شيئا
المقرر العربي يبدأ من الدرجة الثانية ولا يذكر الأولى، فيصعد أكثر القراء هذا السلم مقلوبا.
- 01قس مرة واحدة على ملفك أنتشغل السطر على ملف كبير وانظر إلى الرقم. لا تثبت أي درجة فوق هذه بدونها.
- 02اعرف ثمن كل شكلالقائمة تمشى والقاموس ينادى. الأسماء تنتظر، والأثمان لا تنتظر.
- 03قابل المسألة مرة ثانيةالنمط المقروء قبل المرة الثانية حل معلق على لا شيء.
- 04اكتب للقارئ الثانيالعادات لا تعني شيئا قبل أن يفتح المرء ملفه بعد ثمانية أشهر.
لا تقول كم تستغرق درجة. الأولى تقع في بعد ظهر واحد، وقد لا تقع في سنتين، وذلك وحده سبب الذهاب إليها قصدا.
الجدار هنا مفردات كاملة وقياس واحد لم يحدث
المادة العربية في هذا الفرع أوفر منها في أي فرع آخر من فروع البرمجة، وتلك هي المشكلة لا الحل. أكثرها محاضرة جامعية مصورة أو ملف PDF ممسوح، مكتوب لامتحان: تعريف، ثم رمز، ثم مثال على ورق. فيصل القارئ العربي إلى الأساسيات ومعه مفرداتها كلها، ولم يشغل قياسا واحدا في حياته.
وعلامة هذا الجدار محددة وتفحص اليوم. من يعرف الفرق بين القائمة والقاموس تعريفا، ولا يستطيع أن يقول كم استغرق سطره الأخير، لم يقف بسبب ضعف فيه. وقف لأن ما قرأه لم يكن فيه رقم واحد يقاس. الفرق بين 0.004 و8.7 لا ينتقل في تعريف، ينتقل في تشغيل.
وثمن الجدار يدفع متأخرا: في أول مقابلة عمل فيها سؤال عملي، أو في أول ملف حقيقي يزيد على بضعة آلاف من السطور، وكلاهما يأتي بعد أن يكون القارئ قد صدق أنه أنهى هذا الفرع.
ثلاث أدوات تصل مع Python نفسها، بلا تنزيل ولا حساب ولا بطاقة
القياس في هذه الصفحة كله خرج من أداة واحدة. timeit يقيس السطر وحده، لا بدء المفسر معه، وذلك ما يفصلها عن وضع ساعة حول الملف كله، فتقيس معه فتح الملفات وتحميل الحزم:
python3 -m timeit -s "ids=set(open('ids.txt').read().split())" "'ST-40311' in ids"
وترد سطرا واحدا فيه عدد التكرارات والزمن الوسيط للتكرار الواحد. تغيير set إلى list في ذلك السطر يعيد الفرق الذي في أول الصفحة على أي جهاز.
والأداة الثانية pdb، وهي تحل محل إضافة أمر طباعة ثم إعادة تشغيل الملف من أوله. تفتح بسطر python3 -m pdb script.py، وأنفع أوامرها where، لأنه يطبع سلسلة الدوال الموقوفة. إطار المكدس دالة موقوفة في انتظار دالة أخرى، وقراءة هذه السلسلة هي الفرق بين معرفة مكان الخطأ ومعرفة الطريق الذي أوصل إليه.
والثالثة cProfile، تحل محل التخمين في أي دالة أبطأ ثم حذف أسطر حتى يسرع البرنامج. python3 -m cProfile -s cumtime script.py يرتب الدوال بالزمن المتراكم، وأعلى سطر فيه هو الذي يستحق العمل.
وثلاثتها تصل مع Python ولا تنزل، فلا حساب ولا بطاقة دولية ولا فرق بين الرياض والقاهرة والدار البيضاء والخرطوم. هذه ملاحظة تستحق أن تقال صراحة، لأن أكثر دروس هذا الفرع تبدأ بتنصيب حزمة خارجية لا يحتاجها القياس الأول.
الثمانية التي تحت هذه الصفحة، وبأيها يبدأ
البابان الأولان تحت هذه الصفحة هما كيف يعمل الكود وهياكل البيانات، وترتيبهما بهذا الشكل مقصود: الأول يقول على أي شيء يحاسب الجهاز، والثاني يحول ذلك إلى اختيار يكتب في سطر. واللبنات الأساسية أسرعها مرورا لمن أنهى دورة، وأثقل ما فيها الحلقة وحدها، لأنها الموضع الذي يختبئ فيه الثمن.
وكتابة كود جيد والبرمجة الكائنية يقرآن متأخرين عن قصد، لأن كليهما عن حجم لم يبلغه برنامج من أربعين سطرا: الأول عن القارئ الثاني، والثاني عن تجميع البيانات مع ما يعمل عليها حين تصير الأشياء كثيرة. ومن أراد أن يرى الأثمان نفسها مكتوبة بلغات مختلفة فموضعها لغات البرمجة، ومن أراد أن يخرج ما كتبه من جهازه فموضعه نشر المشاريع.
ويبقى سؤال يصل بعد هذه الصفحة مباشرة، وهو أن ملف ياسمين نصي، وسجل مدرسة حقيقية ليس ملفا نصيا. متى يتوقف الملف عن الكفاية، وما الذي يجيب عن السؤال نفسه حين تكون الصفوف ملايين ولا تدخل الذاكرة أصلا؟ جوابه في قواعد البيانات، وهو ينقل الاختيار من شكل داخل البرنامج إلى فهرس خارجه.