ملحق I — القاموس

الحاوي
Container

المحجَّم
Sized

القابل للكر
Iterable

الجمع
Collection

المقابلة
Mapping

المقابلة المتغيرة
MutableMapping

القاموس
dict

شكل I.1: شجرة أنواع القاموس

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

القاموس (dict) جمع متغير مرتب من المفاتيح المرقَّمة الفريدة، تقابل كل منها قيمةً.

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

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

ولاحظ أن القاموس عبارة عن مجموعة اقترانات أو مقابلات:

يستعمل القاموس في كثير من السياقات في البرمجة، وهو من أهم هياكل البيانات.

استعمالات القاموس

الكلمة ومعناها

القاموس بالمعنى الذي يشير إليه اللفظ. نفترض أن لدينا قاموسًا يحتوي على ترجمة بعض الكلمات من الإنجليزية إلى العربية:

english_to_arabic = {
    'apple': 'تفاحة',
    'banana': 'موزة',
    'orange': 'برتقالة',
}

ربط الأسماء بالأرقام

مثلاً: دليل أرقام الهواتف هو قاموس:

name_to_phone = {
    'Adam': '966xxxxxxxxx',
    'Mohammed': '966xxxxxxxxx',
}

دليل أسماء النطاقات

وكذلك دليل أسماء النطاقات (DNS: Domain Name System) على شبكة الانرتنت هو قاموس:

domain_to_ip = {
    'google.com': '172.217.14.206',
    'wikipedia.org': '103.86.96.10',
}

اختصار الروابط الطويلة

من استعمالات القاموس: اختصار الروابط الطويلة في روابط قصيرة.

short_to_full = {
    'google': 'https://www.google.com',
    'python': 'https://www.python.org',
    'wiki': 'https://www.wikipedia.org',
}

الكلمات المختصرة

أو الاختصارات إلى الكلمة التامة:

abb_to_full = {
    'ASAP': 'As Soon As Possible',
    'BRB': 'Be Right Back',
    'DIY': 'Do It Yourself',
    'EDA': 'Exploratory Data Analysis',
    'FYI': 'For Your Information',
    'SAR': 'Saudi Riyal',
}

القاموس (dict)

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

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

ويقبل القاموس لكونه مقابلة (Mapping) الإجراءات التالية:

  • الإشارة مفتاح: dict[key]
  • الإشارة بمفتاح مع الرجوع بقيمة ابتدائية إن لم يُعثَر عليه: dict.get(key[, default])

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

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

راجع خريطة الجموع: .

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

الفعل عمله
dict.clear() تزيل جميع العناصر من القاموس.
dict.get(key[, default]) ترجع قيمة المفتاح key. إذا لم يكن المفتاح موجودًا، فإنها ترجع default (أو القيمة العدمية None إذا لم يتم توفير قيمة افتراضية default).
dict.items() ترجع عرضًا لعناصر القاموس (أزواج المفتاح والقيمة).
dict.keys() ترجع عرضًا لمفاتيح القاموس.
dict.values() ترجع عرضًا لقيم القاموس.
dict.pop(key[, default]) تزيل المفتاح key وتعيد قيمته. إذا لم يكن المفتاح موجودًا، يتم إرجاع default إذا تم توفيره، وإلا يتم رفع خطأ KeyError.
dict.popitem() تزيل وتعيد زوجًا عشوائيًا (مفتاح، قيمة) من القاموس. يتم ترتيب الأزواج بترتيب LIFO (الأخير يدخل أولاً) في الإصدارات قبل Python 3.7.
dict.setdefault(key[, default]) إذا كان المفتاح key موجودًا في القاموس، فإنه يعيد قيمته. إذا لم يكن كذلك، فإنه يُدخل المفتاح مع القيمة default ويعيد default.
dict.update([other]) تقوم بتحديث القاموس بالمفاتيح/القيم من other، مع الكتابة فوق المفاتيح الموجودة.
dict.fromkeys(iterable[, value]) ينشئ قاموسًا جديدًا بمفاتيح من iterable وقيم تم تعيينها على value (افتراضيًا None).
dict.copy() ترجع نسخة سطحية من القاموس.

وهذه كذلك، كلها قابلة للتجزئة، ولا يشترط أن تكون متجانسة (من نفس النوع)، كما يلي:

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

التضمين

وقد تكون البيانات متضمنة بعضها في بعض، كتفضيلات المستخدم (user):

user = {
    "name": "Adam",
    "language": "Arabic",
    "phone": "966xxxxxxxxx",
    "last_updated": "2021-09-01",
    "age": 25,
    "notifications": {
        "email": "monthly",
        "sms": "weekly",
        "push": "daily"
    },
    "emails": ["example1@domain.com", "example2@domain.com"]
}

فالمفتاح (Key) عادةً ما يكون نصًّا كما ترى. وأما القيمة (Value) فقد تكون نصًّا أو عددًا أو قائمة أو حتى قاموسًا!

وللوصول إلى قيمة مضمَّنة، قد تكتب:

a = user['notifications']
print(a)
{'email': 'monthly', 'sms': 'weekly', 'push': 'daily'}
b = a['sms']
print(b)
weekly

وذلك أن نوع قيمة المتغير a هو قاموس:

type(a)
dict

أو تأتي بها مرة واحدة:

c = user['notifications']['sms']
print(c)
weekly

وكذا الأمر في الوصول للقائمة المضمَّنة (emails) كما يلي:

e = user['emails']
print(e)
['example1@domain.com', 'example2@domain.com']

ثم الوصول إلى عنصرٍ من هذه القائمة:

print(e[0])
example1@domain.com

وذلك أن نوع قيمة المتغير c هو قائمة:

type(e)
list

أو الوصول إليها مرة واحدة:

f = user['emails'][0]
print(f)
example1@domain.com

ومن الأنماط المستعملة بكثرة: استعمال القواميس كعناصر لقائمة. ولذلك وجب علينا التدرب على قراءتها.

فمثلاً هذه قائمة يحتوي كل عنصرٍ منها على قاموس لبيانات مُرَشَّح للتوظيف، ونريد أن نستخلص خبراتهم المهنية منها:

data = [
    {
        'name': 'Ahmad Hamada',
        'experiences': [
            {
                'company': 'Geo Space',
                'start': '2038-01-01',
                'end': '2039-01-01',
                'role': 'Junior Software Engineer',
            },
            {
                'company': 'Space Roots',
                'start': '2039-01-01',
                'end': '2041-01-01',
                'role': 'Senior Software Engineer',
            },
        ],
    },
    {
        'name': 'Belal Banana',
        'experiences': [
            {
                'company': 'Banana Tech',
                'start': '2041-01-01',
                'end': '2042-01-01',
                'role': 'Smoothie Operator',
            },
            {
                'company': 'BugSquash Labs',
                'start': '2042-02-01',
                'end': '2043-08-01',
                'role': 'Code Pest Control Specialist',
            },
            {
                'company': 'Caffeinated Circuits Inc.',
                'start': '2043-09-01',
                'end': '2045-05-01',
                'role': 'Espresso-Driven Engineer',
            }
        ]
    },
]

ولنفترض أننا نريد آخر وظيفة شغلها المرشح الثاني، فإننا نكتبها في بايثون بهذا الشكل:

data[1]['experiences'][-1]['role']
'Espresso-Driven Engineer'

وبايثون تقيِّمها (أي: تحسبها أو تفسِّرها) من اليسار إلى اليمين، على النحو التالي:

من المتغير data أريد العنصر الثاني، ومنه أريد مقابل المفتاح experiences، ومنه أريد العنصر الأخير، ومنه أريد مقابل المفتاح role.

وقد يكون فصلها أوضَح هكذا:

  • من المتغير data (هو قائمة)
  • منه: العنصر الثاني (1) (هو قاموس)
  • منه: مقابل المفتاح experiences (هو قائمة)
  • منه: العنصر الأخير (هو قاموس)
  • منه: مقابل المفتاح role (هو نص)

فإذا أردت أن تقرأها بالعكس من اليمين إلى اليسار، فتقول:

  • مقابل المفتاح role
  • في العنصر الأخير من
  • مقابل المفتاح experiences
  • في العنصر الثاني (1) من
  • المتغير data

وتقول أيضًا: أريد وظيفةَ آخر خبرات الثاني من المرشحين.

نصيحة

قراءة صيَغ الوصول هي من مهارات المبرمج الذي يحتاجها كثيرًا.

الكر

ويحصل الكر بثلاثة طرق:

  • كر المفاتيح: for key in d.keys()
  • كر القيم: for value in d.values()
  • كر العناصر: for key, value in d.items() (ينتج عنها أزواج (2-tuple) من الفاتيح والقيم)

مثال:

d = {
    'A': 'Salam',
    'B': 'Hello',
    'C': 'Hi',
}
for value in d.values():
    print(value)
Salam
Hello
Hi

عكس القاموس

إذا أردت عكس القاموس بحيث يصير المفتاح مكان القيمة والقيمة مكان المفتاح، فبهذه الطريقة:

d = {
    'ASAP': 'As soon as possible',
    'TBD': 'To be determined',
    'IDK': 'I don\'t know',
}
inverse = dict()
for k, v in d.items():
    inverse[v] = k
inverse
{'As soon as possible': 'ASAP',
 'To be determined': 'TBD',
 "I don't know": 'IDK'}

أو بالجملة المختصرة:

inverse = {v: k for k, v in d.items()}
inverse
{'As soon as possible': 'ASAP',
 'To be determined': 'TBD',
 "I don't know": 'IDK'}

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

ويتحصل القاموس من سلسلتين باستعمال الإجراء zip()، كما يلي:

students = ['Ahmad', 'Belal', 'Camal', 'Dawood']
marks    = [     90,      80,      75,       85]
data = dict(zip(students, marks))
data
{'Ahmad': 90, 'Belal': 80, 'Camal': 75, 'Dawood': 85}