7  المجموعة

flowchart TD
    Collection[<b>الجمع</b> <br> <code>Collection</code>]
    Collection --> Set[<b>المجموعة</b> <br> <code>Set</code>]
    Set --> set[<b>المجموعة المتغيرة</b> <br> <code>set</code>]
    Set --> frozenset[<b>المجموعة الجامدة</b> <br> <code>frozenset</code>]
شكل 7.1: شجرة أنواع المجموعة

وراجع خريطة المجموعات: شكل 1

المجموعة (set) هي جمع متغير من عناصر مرقومة فريدة بلا ترتيب.

أو بالقوسين المتعرجين {} أو بالإجراء المُنشئ set() على النحو التالي:

s = {10, 20, 30}
s1 = set((10, 20, 30))
s2 = set([10, 20, 30])
s3 = set(range(10, 30+1, 10))
assert s == s1 == s2 == s3

تقبل المجموعة -لكونها من نوع الجمع (Collection)- العمليات التالية:

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

ولا يشترط تجانس العناصر؛ بل يجوز أن تكون أنواعها مختلفة:

s = {10, 20, 'hello', True, (300, 400)}

ونرى عمليات المجموعة عليها: العد، والعضوية والتكرار:

assert len(s) == 5
assert 'hello' in s
assert 300 not in s
assert (300, 400) in s

for x in s:
    print(x)
hello
True
(300, 400)
20
10

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

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

TypeError: 'set' object is not subscriptable

التحقق السريع

عرفنا أن المجموعة تقبل إجراء العضوية x in s، ولكن ثمة خصوصية لهذا الإجراء في المجموعة. وهذه الخصوصية تكمن في سرعة هذا الإجراء في المجموعة مقارنة بعملة في التسلسل مثلاً:

black_set = {"192.168.1.1", "10.0.0.5", "172.16.0.2"}
ip = "10.0.0.5"
if ip in black_set:
    print("Access Denied")
Access Denied

ومثله في التسلسل:

black_list = ["192.168.1.1", "10.0.0.5", "172.16.0.2"]
ip = "10.0.0.5"
if ip in black_list:
    print("Access Denied")
Access Denied

لكن الفرق هو تفاصيل هذه العملية، إذ تختلف الكيفية خلف الكواليس. ولن يكون الفرق في السرعة واضحًا إلا حين تكون المجموعة كبيرة جدًّا.

  • وذلك أننا في القائمة نحتاج أن نمر على العناصر واحدًا تلو الآخر، حتى نجد العنصر أو لا نجده
  • أما في المجموعة، فإن القيمة يتم رقمها (hash) حتى يُعرف موقع العنصر في المجموعة مباشرة من غير المرور على عناصرها. فعملية الرقم تعطينا العنوان بشكل ذكي.

إن أردت مزيدًا من التفاصيل فإليك هذا الرابط: https://www.w3schools.com/dsa/dsa_data_hashsets.php.

منطق المجموعة الرياضية

مما تتميز به المجموعة عن بقية أنواع الجمع: قبولها المنطق الرياضي على النحو التالي:

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

وهذه القطعة مثال لجميع هذه العمليات الرياضية:

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

العمليات على المجموعات

الاتحاد

اتحاد مجموعتين
set.union(set1, set2)
{1, 2, 3, 4, 5, 6, 7, 8}

التقاطع

تقاطع مجموعتين
set.intersection(set1, set2)
{4, 5}

الفرق

الفرق
set.difference(set1, set2)
{1, 2, 3}
set.difference(set2, set1)
{6, 7, 8}

الفرق التماثلي

الفرق التماثلي
set.symmetric_difference(set1, set2)
{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)

الجزئية والشمول
A = {1, 2, 3}
B = {1, 2, 3, 4, 5, 6}

وهذا مثال لاستعمالها كما في الجدول:

assert A.issubset(B)
assert B.issuperset(A)

الانفصال

وأما الانفصال، فهو عدم وجود أدنى تقاطع بين المجموعتين:

C = {'Apple', 'Banana'}
assert C.isdisjoint(A)
assert C.isdisjoint(B)