طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 101 · تركيب البيانات والخوارزميات
الدرس 15 · الوحدة 7: اختيار تركيب بيانات لحل مشكلة

القاموس والمجموعة: جداول التجزئة

السر وراء سرعة dict و set: حساب مكان العنصر بدل البحث عنه. ⏱ نحو 35 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح فكرة جدول التجزئة ودالة التجزئة.
  • تبني جدول تجزئة بسيطًا وتعالج التصادم بالسلاسل.
  • تحفظ تعقيد عمليات dict و set في المتوسط والأسوأ.
  • تستخدم القاموس والمجموعة للعدّ وحذف التكرار وعمليات المجموعات.

١كيف يجد القاموس المفتاح في خطوة واحدة؟

القاموس dict والمجموعة set مبنيان على جدول التجزئة Hash Table: مصفوفة خانات، ودالة تحوّل المفتاح إلى رقم خانة. فبدل البحث في كل العناصر، نحسب رقم الخانة ونذهب إليها مباشرة.

أحمدسارةخالدمنىhash(k)% 801سارة ← 055223أحمد ← 0551456خالد ← 0553 · منى ← 05547
دالة التجزئة تحوّل المفتاح إلى رقم خانة. وحين يقع مفتاحان في الخانة نفسها (6) يحدث تصادم.
main.py
# دالة التجزئة تحوّل أي قيمة غير قابلة للتعديل إلى عدد صحيحprint(hash(441001))print(hash(441001) % 8)        # رقم الخانة في جدول من 8 خاناتprint(hash((24, 39)) == hash((24, 39)))
Shell×
>>> %Run main.py
441001
1
True
>>>

النصوص تُجزَّأ بقيمة عشوائية تتغير كل مرة يُشغَّل فيها بايثون (لأسباب أمنية)، لذلك لا نطبع hash لنص هنا. المهم: القيمة نفسها تعطي التجزئة نفسها أثناء تشغيل البرنامج.

٢جدول تجزئة نبنيه بأيدينا

جدول بسيط من 8 خانات، كل خانة قائمة صغيرة تحمل أزواج (مفتاح، قيمة). وحين يقع مفتاحان في الخانة نفسها (تصادم Collision) نضعهما معًا في قائمتها، وتسمى هذه الطريقة السلاسل Chaining:

main.py
class HashTable:    def __init__(self, size=8):        self.buckets = [[] for _ in range(size)]     def _index(self, key):        return sum(ord(ch) for ch in str(key)) % len(self.buckets)   # دالة تجزئة بسيطة     def put(self, key, value):        b = self.buckets[self._index(key)]        for pair in b:            if pair[0] == key:                pair[1] = value          # تحديث                return        b.append([key, value])     def get(self, key):        for k, v in self.buckets[self._index(key)]:            if k == key:                return v        return None phones = HashTable()phones.put("أحمد", "0551")phones.put("سارة", "0552")phones.put("خالد", "0553")print(phones.get("سارة"), phones.get("فهد"))for i, b in enumerate(phones.buckets):    if b:        print(i, b)
Shell×
>>> %Run main.py
0552 None
0 [['خالد', '0553']]
4 [['أحمد', '0551'], ['سارة', '0552']]
>>>

٣التعقيد: O(1) في المتوسط

العمليةالمتوسطالأسوأمتى الأسوأ؟
البحث d[k] و k in dO(1)O(n)كل المفاتيح في خانة واحدة (دالة تجزئة سيئة)
الإضافة والتعديلO(1)O(n)كذلك
الحذفO(1)O(n)كذلك

بايثون يستخدم دالة تجزئة جيدة جدًّا، ويكبّر الجدول تلقائيًّا حين يمتلئ (مثل المصفوفة الديناميكية)، فتبقى العمليات O(1) عمليًّا.

٤استخدامات قوية

main.py
# 1) العدّ: كم مرة ظهرت كل كلمة؟  O(n)text = "قهوة شاي قهوة ماء قهوة شاي"count = {}for w in text.split():    count[w] = count.get(w, 0) + 1print(count) # 2) حذف التكرار مع حفظ الترتيبseen, unique = set(), []for w in text.split():    if w not in seen:        seen.add(w)        unique.append(w)print(unique) # 3) عمليات المجموعاتa = {"أحمد", "سارة", "خالد"}      # مسجلون في 101b = {"سارة", "منى", "خالد"}       # مسجلون في 121print(sorted(a & b), sorted(a - b), len(a | b))
Shell×
>>> %Run main.py
{'قهوة': 3, 'شاي': 2, 'ماء': 1}
['قهوة', 'شاي', 'ماء']
['خالد', 'سارة'] ['أحمد'] 4
>>>
main.py
from collections import Counterprint(Counter("قهوة شاي قهوة ماء قهوة شاي".split()).most_common(2))
Shell×
>>> %Run main.py
[('قهوة', 3), ('شاي', 2)]
>>>

توقّع وتحقّق

متوسط تعقيد k in d:

جدول تجزئة.

وقوع مفتاحين في الخانة نفسها:

Collision.

أيها لا يصلح مفتاحًا للقاموس؟

list قابلة للتعديل.

الطلاب المسجلون في المقررين معًا:

التقاطع.

الخلاصة

تحدٍّ برمجي: أول حرف لا يتكرر

متوسط

اكتب دالة first_unique(text) تُرجع أول حرف لا يتكرر في النص، أو None إن لم يوجد، بتعقيد O(n): مرور للعدّ ثم مرور للبحث.

المطلوب:
  • المرور الأول: عدّ كل حرف في قاموس.
  • المرور الثاني: أول حرف عدده 1.
  • اطبع النتيجة لكل نص: "سلسبيل" و "مرمر" و "تراكيب".

ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:

main.py
def first_unique(text):    pass
المطلوب×
سلسبيل ب
مرمر None
تراكيب ت
💡 تلميح 1
count.get(ch, 0) + 1
💡 تلميح 2
لو بحثت بـ text.count(ch) لكل حرف لصار الحل O(n²).

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

main.py
def first_unique(text):    count = {}    for ch in text:        count[ch] = count.get(ch, 0) + 1    for ch in text:        if count[ch] == 1:            return ch    return None for t in ["سلسبيل", "مرمر", "تراكيب"]:    print(t, first_unique(t))
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس