Hash Maps في Python: ازاي dict بيلاقي مفتاح من 50 مليون في 180 نانو ثانية
لو عندك dict فيه 50 مليون مفتاح وبتعمل users[email]، Python بيرجّعلك القيمة في حوالي 180 نانو ثانية. لو نفس البحث على list بنفس الحجم، هتاخد 47 ثانية. الفرق 260 مليون مرة. ده مش سحر — ده هيكل بيانات اسمه Hash Map، وdict في Python كله مبني عليه. في المقال ده هتفهم بالظبط ازاي الفرق ده بيحصل، وامتى dict بيتحوّل من O(1) لـ O(n) بدون ما تحس.
المشكلة باختصار
أي تطبيق فيه lookup متكرر — تحقّق من session، cache في الذاكرة، deduplication، تجميع counters — بيعتمد على dict. لو فهمت ازاي dict بيشتغل من جوّا، هتعرف ليه بعض الـ keys بتبطّأ التطبيق فجأة، وليه استبدال tuple بـ frozen dataclass ممكن يوفّر 30% من زمن الـ lookup.
مثال للمبتدئ: خزانة مفاتيح الفندق
تخيّل فندق فيه 1000 صندوق مفاتيح في الاستقبال، مرقّمة من 0 لـ 999. لو نزيل جديد جالك واسمه "أحمد"، ممكن تروح تدوّر في الصناديق واحد واحد لحد ما تلاقي مفتاحه. ده هياخد منك في المتوسط 500 محاولة. الطريقة الذكية: تطبّق قاعدة على الاسم تطلّعلك رقم الصندوق دايركت. مثلاً: اجمع قيم حروف الاسم، خد الباقي على 1000. "أحمد" بقاعدة بسيطة بيطلع 247. تروح صندوق 247، تلاقي المفتاح. محاولة واحدة بدل 500.
القاعدة دي اسمها hash function. خزانة الصناديق اسمها buckets array. لو نزيلين اسمهم بيطلعهم نفس الرقم، ده اسمه collision. dict في Python بيشتغل بنفس المنطق بالظبط، بس على مستوى الذاكرة وبسرعة CPU instructions.
التعريف العلمي الدقيق
Hash Map هي بنية بيانات بتربط key بـ value عبر دالة hash(key) -> integer ثم index = hash % capacity. في CPython 3.12، dict مبني على open addressing مش chaining: لو الـ index محجوز، بيدوّر على بديل بصيغة perturbation معروفة (i = (5*i + 1 + perturb) % capacity) لحد ما يلاقي خانة فاضية. الـ load factor الأقصى 2/3؛ لما يتعدى، dict بيعمل resize ويضاعف الحجم. ده اللي بيخلّي الـ amortized cost للـ insert والـ lookup ثابت — O(1) في المتوسط، O(n) في أسوأ حالة (هجوم hash collision أو keys مصمّمة بحقد).
قياس فعلي بالنانو ثانية
الكود ده شغّال على Python 3.12 ويقيس الفرق بين dict و list على 50 مليون مفتاح: