7  المجموعة المرقومة

بعد أن رأينا التلسلسل الذي هو نوع مجموعة مرتبة. ننتقل في درسنا هذا للبحث في نوعين آخرين يندرجان تحت المجموعة المرقومة:

7.1 مجموعة الفرائد

مجموعة الفرائد (set) هي مجموعة متغيرة تحوي أشياء مرقومة فريدة بلا ترتيب.

  • متغيرة: يعني أنها تقبل الإضافة والحذف والتعديل على عناصرها
  • فريدة: يعني أن العنصر لا يتكرر فيها
  • مرقومة: يعني أن عناصرها يجب أن تكون قابلة للرقم (hash)
  • بلا ترتيب: يعني أن العنصر ليس له موقع محدد فيها فليس له ما قبله وليس له ما بعده

انظر MutableSet في خريطة المجموعات: شكل 1.

تنشأ المجموعة الفريدة بالفعل المنشئ set(). ويفضل استعمال القوسين المتعرجين { } بدلاً منه:

s1 = set([10, 20, 30])
s2 = {10, 20, 30}
assert s1 == s2

تقبل مجموعة الفرائد لكونها مجموعة (Collection) العمليات التالية:

  • العد: len(s)
  • التكرار: for x in s
  • العضوية: x not in s

وباعتبارها مجموعة متغيرة (MutableSet)، فإنها تقبل الأفعال التالية:

  • الإضافة: add
  • الحذف: discard
  • نزع عنصر عشوائي: pop
  • المحو: clear

ذكرنا أن شرط عضوية العنصر أن يكون مرقومًا؛ وهذا يجعل عملية البحث فيها قفزة واحدة (O(1))؛ إذْ يتم حساب الرقم بالفعل hash() الذي يشير لعنوان القيمة في الذاكرة.

المرقوم (Hashable): هو ما يقبل الفعل hash() الذي يحول الشيء إلى رقم فريد. ومنه:

  • النص (str) له خوارزمية معيَّنة لرقْمه
  • الرقم (int) رقْمه هو نفسه
  • الصف (tuple) رقْمه هو رقم عناصره (فيجب أن تكون جميع عناصره مرقومة)

أما القائمة مثلاً، فليست معدودة ضمن المرقومات، وذلك لأنها متغيرة (تقبل زيادة أو حذف أو تعديل عنصر) وذلك يغير الرقم؛ والشرط في المرقوم ألا يتغير رقمه أبدًا؛ لذا تجد الصف معدودًا ضمن المرقومات وليست القائمة كذلك:

s = {10, 'AAA', True, (22, 'BB')}

assert len(s) == 4
assert 22 not in s
assert (22, 'BB') in s

for x in s:
    print(x)
(22, 'BB')
True
10
AAA

باعتبارها مجموعة غير مرتبة، فإنها لا تسجل موقع العنصر أو ترتيب إدراجه. وبالتالي، فإنها لا تقبل الإشارة (xs[i]) أو التقطيع (xs[i:j]) أو أي سلوك يشبه المتسلسلات. لاحظ الخطأ التالي:

xs = {10, 20, 30}
xs[0]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 2
      1 xs = {10, 20, 30}
----> 2 xs[0]

TypeError: 'set' object is not subscriptable

وتقبل المجموعة الفريدة ما تقبله المجموعة في الرياضيات من عمليات:

  • التقاطع والاتحاد والفرق، والفرق التماثلي
  • وكذلك تحقق: (الجزئية والشمول والانفاصل).

وهذا الكود مثال لجميع هذه العمليات الرياضية:

set1 = {1, 2, 3, 4, 5}
set2 =          {4, 5, 6, 7, 8}

union = set1 | set2
assert union == {1, 2, 3, 4, 5, 6, 7, 8}

intersection = set1 & set2
assert intersection == {4, 5}

diff1 = set1 - set2
assert diff1 == {1, 2, 3}

diff2 = set2 - set1
assert diff2 == {6, 7, 8}

symmetric_difference = set1 ^ set2
assert symmetric_difference == {1, 2, 3, 6, 7, 8}

نصيحة: من الأفضل استعمال اسم الفعل بدلاً من العلامة التي تقابله حيث أنها تقبل أي نوع من المتكررات (Iterables) ولا تقتصر على مجموعة الفرائد فقط (set).

العملية العلامة الفعل المكافئ
الاتحاد set1 | set2 set1.union(set2)
التقاطع set1 & set2 set1.intersection(set2)
الفرق set1 - set2 set1.difference(set2)
الفرق التماثلي set1 ^ set2 set1.symmetric_difference(set2)

وكذلك لدينا أفعال تحقق الجزئية والشمول والانفصال:

العملية العلامة الفعل المكافئ
تحقق الجزئية set1 <= set2 set1.issubset(set2)
تحقق الشمول set1 >= set2 set1.issuperset(set2)
تحقق الانفصال len(set1 & set2) == 0 set1.isdisjoint(set2)

وهذا مثال لاستعمالها:

set1 = {'A', 'B', 'C'}
set2 = {'A', 'B', 'C', 'D', 'E'}
set3 = {'سين', 'جيم', 'قاف'}

assert (set1 <= set2) == set1.issubset(set2)
assert (set2 >= set1) == set2.issuperset(set1)
assert (
    set3.isdisjoint(set1 | set2) ==
    (len(set3 & (set1 | set2)) == 0)
)

وهنا بحثٌ سريع جدًا باستعمال جملة التحقق من العضوية (بالتأكيد لا يظهر أثر السرعة في مجموعة صغيرة):

languages = {"Arabic", "English"}
if 'Python' not in languages:
    print('you need to add Python to your languages!')
you need to add Python to your languages!

وتستعمل كذلك لإزالة العناصر المتكررة في أي مجموعة، نحو الكود التالي. فإننا نحول القائمة إلى مجموعة فرائد فتزول تلك العناصر تلقائيًّا، ثم نعيدها كما كانت:

numbers = [1, 2, 2, 3, 4, 4, 5]
unique_numbers = list(set(numbers))
print(unique_numbers)
[1, 2, 3, 4, 5]

وكذلك من الصف:

t = (1, 2, 2, 3, 4, 4, 5)
unique_t = tuple(set(t))
print(unique_t)
(1, 2, 3, 4, 5)

7.2 القاموس

القاموس (dict) هو مجموعة مرتبة من المرقومات الفريدة الدالة على قيم.

انظر MutableMapping في خريطة المجموعات: شكل 1.

تنشأ المجموعة الرابطة بالفعل المنشئ dict() ويفضل استعمال القوسين المتعرجين { } بدلاً منه:

d1 = dict(key1='value1', key2='value2')
d2 = {'key1': 'value1', 'key2': 'value2'}
assert d1 == d2

اصطلاحات: نسمي key1 المرقوم ونسمي value1 القيمة؛ وهما معًا نسميهما رابطًا.

ومن حيث كون القاموس مجموعة (Collection)، فإنه يقبل الأفعال ثلاثة:

  • العضوية: x not in d
  • العد: len(d)
  • التكرار: for x in d

ويقبل القاموس لكونه دالة (Mapping) الأفعال التالية:

  • الإشارة بمرقوم: dict[key]
  • الإشارة بمرقوم أو إرجاع قيمة ابتدائية: dict.get(key[, default])

ويجوز التكرار بثلاثة طرق:

  • كر الروابط: for key, value in d.items()
  • كر المرقومات: for key in d.keys()
  • كر القيم: for value in d.values()

ولكونه دالة متغيرة (MutableMapping)، فإنه يقبل الأفعال التالية:

  • التعديل بمرقوم: dict[key] = value
  • الحذف بمرقوم: del dict[key]
  • نزع بمرقوم وإرجاع القيمة: x = dict.pop(key)
  • التحديث بدالَّة أو بمكرر روابط: dict.update(mapping)

ملاحظة: منذ بايثون 3.7 أصبح القاموس مرتبًا. أي أن الروابط لها ما قبلها ولها ما بعدها بحسب ترتيب إدراجها.

لاحظ أننا نستعمل مرقومات مختلفة وأيضًا نستعمل قيم مختلفة:

data = {
    'key1': 100,
    20: 'value2',
    'c': [10, 20, 30, True],
    (1, 2): 'value3',
    ('a', 'b', 'c'): 'value4',
}

assert len(data) == 5
del data['key1']
assert 'key1' not in data

ونحصل عليها بالإشارة بمرقوم:

print(data[20])
print(data['c'])
print(data[(1, 2)])
print(data[('a', 'b', 'c')])
value2
[10, 20, 30, True]
value3
value4

إنشاء قاموس من سلسلتين

ويتحصل القاموس من سلسلتين باستعمال الفعل zip()، كما يلي:

students = ['Ahmad', 'Belal', 'Careem', 'David']
marks = [90, 80, 75, 85]
data = dict(zip(students, marks, strict=True))
data
{'Ahmad': 90, 'Belal': 80, 'Careem': 75, 'David': 85}