Kodax

Collections المتقدمة — هياكل البيانات

وحدة collections

القوائم والقواميس تكفي لمعظم المهام، لكن بعض الأنماط تتكرر كثيراً لدرجة أن Python وفّرت لها أنواعاً جاهزة في وحدة collections. استخدامها يجعل الكود أقصر وأوضح وأقل أخطاءً.

Counter: العدّ

بدلاً من نمط counts[x] = counts.get(x, 0) + 1:

from collections import Counter
votes = Counter(["أ", "ب", "أ", "ج", "أ"])
votes["أ"]              # 3
votes.most_common(2)    # [('أ', 3), ('ب', 1)]
votes["د"]              # 0 (لا خطأ للعناصر غير الموجودة)

defaultdict: قيمة افتراضية تلقائية

from collections import defaultdict
groups = defaultdict(list)
for name, city in [("أحمد", "بغداد"), ("منى", "البصرة"), ("علي", "بغداد")]:
    groups[city].append(name)

عند الوصول لمفتاح غير موجود يُنشأ تلقائياً بقيمة list() (أي []). مع int تكون القيمة 0، ومع set مجموعة فارغة.

namedtuple: tuple بأسماء حقول

from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
p.x, p.y      # أوضح من p[0], p[1]

خفيفة وغير قابلة للتعديل. للبنى الأكبر ستتعلم dataclass في دورة OOP.

deque: إضافة وحذف سريع من الطرفين

list.pop(0) و list.insert(0, x) بطيئتان مع القوائم الكبيرة لأن كل العناصر تتحرك. deque سريعة من الطرفين:

from collections import deque
queue = deque()
queue.append("طلب 1")      # دخول الطابور
first = queue.popleft()     # الخروج من المقدمة
recent = deque(maxlen=3)    # يحتفظ بآخر 3 عناصر فقط

مثالية للطوابير (FIFO) وسجل "آخر N عمليات".

OrderedDict

منذ Python 3.7 القاموس العادي يحفظ الترتيب، لذلك نادراً ما تحتاج OrderedDict اليوم إلا لميزات خاصة مثل move_to_end.

متى تستخدم ماذا؟

| تحتاج | استخدم |

|---|---|

| عدّ التكرارات | Counter |

| تجميع عناصر حسب مفتاح | defaultdict(list) |

| سجل خفيف بأسماء حقول | namedtuple |

| طابور أو آخر N عناصر | deque |

تمرين

من نص طويل: اطبع أكثر 5 كلمات تكراراً (Counter)، وجمّع الكلمات حسب حرفها الأول (defaultdict)، واحتفظ بآخر 3 كلمات قرأتها (deque).

from collections import Counter, defaultdict, namedtuple, deque

# Counter - عداد
text = "hello world"
letter_count = Counter(text)
print(letter_count)  # Counter({'l': 3, 'o': 2, ...})
print(letter_count.most_common(3))  # أكثر 3 تكراراً

words = ["تفاح", "موز", "تفاح", "برتقال", "موز", "تفاح"]
word_count = Counter(words)
print(word_count["تفاح"])  # 3

# defaultdict - قاموس بقيمة افتراضية
normal_dict = {}
# normal_dict["key"] += 1  # KeyError!

dd = defaultdict(int)  # القيمة الافتراضية 0
dd["key"] += 1
print(dd["key"])  # 1
print(dd["nonexistent"])  # 0

# تجميع حسب فئة
students = [("أحمد", "A"), ("محمد", "B"), ("علي", "A")]
grades = defaultdict(list)
for name, grade in students:
    grades[grade].append(name)
print(dict(grades))  # {'A': ['أحمد', 'علي'], 'B': ['محمد']}

# namedtuple - tuple بأسماء
Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)
print(p.x, p.y)  # 10 20
print(p[0], p[1])  # يعمل كـ tuple عادي

Person = namedtuple('Person', 'name age city')
person = Person("أحمد", 25, "بغداد")
print(person.name)  # أحمد

# deque - قائمة ثنائية الطرف
d = deque([1, 2, 3])
d.append(4)       # إضافة يمين
d.appendleft(0)   # إضافة يسار
print(d)  # deque([0, 1, 2, 3, 4])

d.pop()           # حذف من اليمين
d.popleft()       # حذف من اليسار
print(d)  # deque([1, 2, 3])

# deque بحجم محدد (للتاريخ مثلاً)
history = deque(maxlen=5)
for i in range(10):
    history.append(i)
print(history)  # deque([5, 6, 7, 8, 9])

# تدوير
d = deque([1, 2, 3, 4, 5])
d.rotate(2)   # تدوير يمين
print(d)  # deque([4, 5, 1, 2, 3])
d.rotate(-2)  # تدوير يسار
print(d)  # deque([1, 2, 3, 4, 5])
اختبر فهمك