ملحق H — المجموعة

شجرة أنواع المجموعة في بايثون.

flowchart BT
    Container[<b>الحاوي</b><br>Container]
    Sized[<b>المحجَّم</b><br>Sized]
    Iterable[<b>القابل للكر</b><br>Iterable]
    Collection[<b>الجمع</b><br>Collection]
    Set[<b>المجموعة</b> <br>Set]
    set[<b>المجموعة</b> <br><code>set</code>]
    MutableSet[<b>المجموعة المتغيرة</b> <br>MutableSet]
    frozenset[<b>المجموعة الجامدة</b> <br><code>frozenset</code>]
    
    Collection --> Container
    Collection --> Sized
    Collection --> Iterable
    Set --> Collection
    frozenset --> Set
    MutableSet --> Set
    set --> MutableSet
شكل H.1: شجرة أنواع المجموعة

للتفصيل راجع خريطة الجموع: شكل P.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)، فإنها تقبل الإجراءات التالية:

وإليك جدول طرق المجموعة:

الفعل عمله
set.add(elem) يضيف العنصر elem إلى المجموعة.
set.remove(elem) يزيل العنصر elem من المجموعة. يرفع خطأ KeyError إذا لم يكن elem موجودًا.
set.discard(elem) يزيل العنصر elem من المجموعة إذا كان موجودًا.
set.pop() يزيل ويعيد عنصرًا عشوائيًا من المجموعة.
set.clear() يزيل جميع العناصر من المجموعة.
set.update(*others) يقوم بتحديث المجموعة بإضافة جميع العناصر من المجموعات الأخرى.
set.intersection(*others) ترجع مجموعة جديدة تحتوي على العناصر المشتركة بين المجموعة والمجموعات الأخرى.
set.intersection_update(*others) تقوم بتحديث المجموعة بإبقاء العناصر الموجودة في المجموعة والمجموعات الأخرى فقط.
set.difference(*others) ترجع مجموعة جديدة تحتوي على العناصر الموجودة في المجموعة ولكنها غير موجودة في المجموعات الأخرى.
set.difference_update(*others) تقوم بتحديث المجموعة بإزالة العناصر الموجودة في المجموعات الأخرى.
set.symmetric_difference(other) ترجع مجموعة جديدة تحتوي على العناصر الموجودة في المجموعة أو المجموعة الأخرى ولكن ليس في كليهما.
set.symmetric_difference_update(other) تقوم بتحديث المجموعة بإبقاء العناصر الموجودة في المجموعة أو المجموعة الأخرى ولكن ليس في كليهما.
set.isdisjoint(other) ترجع True إذا لم يكن للمجموعة أي عناصر مشتركة مع المجموعة الأخرى، و False خلاف ذلك.
set.issubset(other) ترجع True إذا كانت المجموعة مجموعة فرعية من المجموعة الأخرى، و False خلاف ذلك.
set.issuperset(other) ترجع True إذا كانت المجموعة مجموعة شاملة للمجموعة الأخرى، و False خلاف ذلك.
set.copy() ترجع نسخة سطحية من المجموعة.

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

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)
True
hello
20
(300, 400)
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) حتى يُعرف رقم العنصر في المجموعة مباشرة من غير المرور على عناصرها. فعملية التجزئة هذه تعطينا العنوان بشكل رياضي.

وهذا يدخل في دراسة الخوارزميات وهيكلة البيانات. فإن أردت مزيد تفصيل فراجع هذه المادة: W3Schools DSA Hash Sets.

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

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

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

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

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)