اذهب إلى المحتوى

التعرف على الخرائط Maps في لغة جو Go


هدى جبور

تمتلك معظم لغات البرمجة نوع بيانات يُمثّل بنية معطيات يُعرف بالقاموس dictionary أو التجزئة hash، إذ تربط هذه البنية أو هذا النوع البيانات على صورة أزواج (مفتاح-قيمة).

تُعَدّ الخرائط أو الروابط maps مفيدةً للغاية في كثير من الحالات مثل أن تريد إعطاء كل طالب رقمًا يُميّزه عن باقي الطلاب، إذ يمكنك في هذه الحالة تعريف رابطة يربط اسم كل طالب (قيمة) برقم يُميّزه (مفتاح)، فالخرائط Maps في لغة جو هي بنية معطيات توجد في لغات أخرى مثل بايثون، وتُعرّف الخرائط في لغة جو من خلال وضع الكلمة map متبوعةً بنوع بيانات المفاتيح (في مثالنا السابق سيكون أعداد صحيحة) ضمن قوسين [ ] ثم نوع بيانات القيم (سلاسل نصية في مثالنا)، وأخيرًا توضع أزواج (مفتاح، قيمة) ضمن قوسين معقوصين {}:

map[key]value{}

سيوضّح المثال التالي الأمر أكثر؛ إذ سنعرّف رابطة مفاتيحه هي الاسم name والحيوان animal واللون color والموقع location، وقيمه هي Sammy و shark و blue و ocean على التوالي.

map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

لاحظ أنّ المفتاح يُوضَع على اليسار والقيمة على اليمين ويُفصل بينهما بنقطتين : كما يُفصل بين كل زوج (مفتاح:قيمة) بفاصلة , ولاحظ أيضًا أننا استخدمنا نوع البيانات string لكل من القيم والمفاتيح، وهذا ليس إجباريًا، لكنه يعتمد على ما ستستخدِمه من بيانات.

يمكنك تخزين الخرائط ضمن متغيرات كما في أيّ نوع بيانات آخر:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(sammy)

يكون الخرج كما يلي:

map[animal:shark color:blue location:ocean name:Sammy]

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

الوصول إلى عناصر الرابطة

يمكننا الوصول إلى قيم محدَّدة في الرابطة بالرجوع إلى المفاتيح المرتبطة بها، فإذا أردنا مثلًا الحصول على اسم المستخدِم من الرابطة sammy، فيمكننا ذلك عن طريق كتابة ["sammy["name كما يلي:

fmt.Println(sammy["name"])

يكون الخرج كما يلي:

Sammy

تتصرف الخرائط مثل قواعد البيانات، لكن بدلًا من فهرسة العناصر بأعداد صحيحة كما هو الحال في الشرائح slice، فإنها تسند لمفتاح، ويمكنك عبر تلك المفاتيح الحصول على القيم المقابلة لها، أي باستدعاء المفتاح "name"، سنحصل على القيمة المرتبطة به وهي "Sammy"، وبالمِثل، فيمكن استدعاء القيم الأخرى في الرابطة sammy باستخدام الصيغة ذاتها:

fmt.Println(sammy["animal"])
//shark تُعيد

fmt.Println(sammy["color"])
//blue تُعيد

fmt.Println(sammy["location"])
//ocean تُعيد

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

المفاتيح والقيم keys/values

لا تمتلك لغة جو أيّة دوال مُضمّنة تمكّنك من سرد جميع المفاتيح أو القيم، ففي بايثون مثلًا هناك الدالة ()‎.kyes التي تعرض لك جميع المفاتيح التي تتضمنها الرابطة أو ()‎.values التي تعرض كل القيم، لكن يمكنك التكرار على عناصر الرابطة في جو من خلال العامِل range كما يلي:

for key, value := range sammy {
    fmt.Printf("%q is the key for the value %q\n", key, value)
}

ستُعيد range هنا قيمتين من أجل كل زوج في الرابطة، إذ ستُمثِّل القيمة الأولى المفتاح key والثانية القيمة value، كما أنّ نوع بيانات key سيكون نفسه نوع بيانات key في الرابطة sammy والأمر نفسه بالنسبة لمتغير القيمة value، وبالتالي سيكون الخرج كما يلي:

"animal" is the key for the value "shark"
"color" is the key for the value "blue"
"location" is the key for the value "ocean"
"name" is the key for the value "Sammy"

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

keys := []string{}

for key := range sammy {
    keys = append(keys, key)
}
fmt.Printf("%q", keys)

وبالتالي سنحصل على شريحة بجميع المفاتيح:

["color" "location" "name" "animal"]

كما ذكرنا هو أنّ المفاتيح غير مرتبة، ولكن يمكنك ترتيبها إذا أردت من خلال الدالة ()sort.Strings:

sort.Strings(keys)

يكون الخرج كما يلي:

["animal" "color" "location" "name"]

يمكنك إجراء الأمر نفسه لاستخراج القيم، لكن هنا سنعرّف شريحةً يكون عدد عناصرها هو عدد الأزواج الموجودة في الرابطة وذلك لكي لا نضطر في كل مرة إلى طلب حجز مساحة إضافية كما فعلنا في المثال السابق حين عرّفنا شريحةً فارغةً ثم في كل تكرار وسّعنا حجمها بإضافة عنصر جديد.

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}

items := make([]string, len(sammy))

var i int

for _, v := range sammy {
    items[i] = v
    i++
}
fmt.Printf("%q", items)

إذًا عرّفنا شريحةً بالحجم المطلوب تمامًا بحيث يقابل عدد عناصر الرابطة (كل عنصر يمثّل زوج)، ثم نُعرّف متغير الفهرسة i وندخل في حلقة التكرار على عناصر الرابطة، ولاحظ أنه وضعنا عامِل _ في بداية الحلقة إشارةً إلى أن المفتاح لا نريده، ويكون الخرج كما يلي:

["ocean" "Sammy" "shark" "blue"]

يمكنك استخدام الدالة المضمّنة len لمعرفة عدد العناصر الموجودة في الرابطة كما يلي:

sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
fmt.Println(len(sammy))

يكون الخرج كما يلي:

4

تفقد وجود عنصر في رابطة

عند محاولة الوصول إلى عنصر غير موجود في القائمة، ستُعيد جو 0 إشارةً إلى أن هذا العنصر غير موجود في الرابطة، ولهذا السبب ستحتاج إلى طريقة بديلة للتمييز بين الصفر المخزن والمفتاح المفقود، لذا انظر إلى المثال التالي، إذ سنعرّف رابطةً فارغةً ثم سنحاول الوصول إلى عنصر غير موجود:

counts := map[string]int{}
fmt.Println(counts["sammy"])

نظرًا لأن المفتاح sammy غير موجود، فستعيد جو 0 كما ذكرنا، فهو يعطي قيمة صفرية تلقائيًا لجميع المتغيرات التي ليس لديها قيمة:

0

هذا غير مرغوب فيه وقد يؤدي إلى حدوث خلل في برنامجك في الكثير من الحالات، ولحل المشكلة هناك قيمة اختيارية ثانية يمكنك الحصول عليها عند البحث عن عنصر في الرابطة، وهي قيمة منطقية bool تُدعى ok وتكون قيمتها true إذا عُثِر على العنصر أو false إذا لم يُعثر على العنصر، وتستخدَم كما يلي:

count, ok := counts["sammy"]

يمكنك إذا أحببت استخدام هذه القيمة المُعادة ok لطباعة رسالة تُرشدك فيما إذا كان العنصر موجودًا أم لا:

if ok {
    fmt.Printf("Sammy has a count of %d\n", count)
} else {
    fmt.Println("Sammy was not found")
}

يكون الخرج كما يلي:

Sammy was not found

يمكنك في جو الجمع بين التصريح عن متغير والفحص الشرطي مع كتلة if/else، إذ يتيح لك هذا اختصار التعليمات البرمجية لإنجاز عملية الفحص:

if count, ok := counts["sammy"]; ok {
    fmt.Printf("Sammy has a count of %d\n", count)
} else {
    fmt.Println("Sammy was not found")
}

عند محاولة الوصول لعنصر ضمن رابطة ما في جو، من الأفضل التحقق من وجودها لتجنب الزلات أو الأخطاء البرمجية في برنامجك.

تعديل الخريطة Map

تُعَدّ الخرائط هياكل بيانات قابلة للتغيير mutable، أي يمكن تعديلها، وسنتعلم في هذا القسم كيفية إضافة عناصر إلى رابطة وكيفية حذفها.

إضافة وتغيير عناصر الرابطة

يمكنك إضافة أزواج قيمة-مفتاح إلى رابطة دون استخدام توابع أو دوال باستخدام الصياغة التالية، حيث نضع اسم متغير الرابطة متبوعةً بالمفتاح بين قوسين [] متبوعة بالعامل = ثم القيمة:

map[key] = value

سنضيف في المثال التالي زوج مفتاح-قيمة إلى رابطة تُسمى usernames:

usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}

usernames["Drew"] = "squidly"
fmt.Println(usernames)

يكون الخرج كما يلي:

map[Drew:squidly Jamie:mantisshrimp54 Sammy:sammy-shark]

لاحظ أنّ الرابطة حُدّثت بالزوج Drew:squidly نظرًا لأنّ الخرائط غير مرتبة، فيمكن أن يظهر الزوج المُضاف في أيّ مكان في مخرجات الرابطة، فإذا استخدمنا الرابطة usernames لاحقًا في ملف برنامجك، فسيظهر فيه الزوج المضاف حديثًا.

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

سنعرِّف في المثال التالي رابطة باسم followers لتعقّب متابِعي المستخدِمين على شبكة معيّنة، إذ حصل المستخدِم "drew" على عدد من المتابعين الإضافيين اليوم، لذلك سنحدّث القيمة المرتبطة بالمفتاح "drew" ثم سنستخدِم الدالة Println()‎ للتحقق من تعديل الرابطة:

followers := map[string]int{"drew": 305, "mary": 428, "cindy": 918}
followers["drew"] = 342
fmt.Println(followers)

يكون الخرج كما يلي:

map[cindy:918 drew:342 mary:428]

نرى في الخرج السابق أنّ عدد المتابعين قد قفز من 305 إلى 342.

a يمكننا استخدام هذه الطريقة لإضافة أزواج قيمة-مفتاح إلى الخرائط عبر مدخلات المستخدِم، إذَا سنكتب برنامجًا سريعًا usernames.go يعمل من سطر الأوامر ويسمح للمستخدِم بإضافة الأسماء وأسماء المستخدِمين المرتبطة بها:

package main

import (
    "fmt"
    "strings"
)

func main() {
    usernames := map[string]string{"Sammy": "sammy-shark", "Jamie": "mantisshrimp54"}

    for {
        fmt.Println("Enter a name:")

        var name string
        _, err := fmt.Scanln(&name)

        if err != nil {
            panic(err)
        }

        name = strings.TrimSpace(name)

        if u, ok := usernames[name]; ok {
            fmt.Printf("%q is the username of %q\n", u, name)
            continue
        }

        fmt.Printf("I don't have %v's username, what is it?\n", name)

        var username string
        _, err = fmt.Scanln(&username)

        if err != nil {
            panic(err)
        }

        username = strings.TrimSpace(username)

        usernames[name] = username

        fmt.Println("Data updated.")
    }
}

نُعرّف أولًا الرابطة ضمن ملف usernames.go ، ثم نضبط حلقة التكرار على الأسماء، وتطلب بعد ذلك من المستخدِم إدخال اسم وتصرّح عن متغير لتخزِّنه فيه، ويجب أن تتحقق بعد ذلك من حدوث خطأ في عملية الإدخال من المستخدِم، ففي حال حدث خطأ، فستخرج من البرنامج.

تلتقط الدالة Scanln المُدخلات التي يدخلها المستخدِم بالكامل بما في ذلك محرف العودة إلى بداية السطر Carriage return، لذا أنت بحاجة إلى إزالة أي فراغ زائد من الدخل باستخدام الدالة strings.TrimSpace.

تتحقق كتلة if من وجود الاسم في الرابطة أو لا، فإذا لم يكن الاسم موجودًا، فسيطبع ملاحظات للمستخدِم تُشير إلى ذلك ثم يطلب إدخال اسم جديد، ويتحقق البرنامج مرةً أخرى من وجود خطأ، ففي حال لم يكن هناك خطأ، فسيَقص محرف الإرجاع ويُسند الاسم المُدخل من المستخدِم على أساس قيمة لمفتاح الاسم ثم يطبع الملاحظات التي تفيد بأن البيانات قد حُدّثت.

سننفِّذ البرنامج من سطر الأوامر:

$ go run usernames.go

سترى الخرج التالي:

Enter a name:
Sammy
"sammy-shark" is the username of "Sammy"
Enter a name:
Jesse
I don't have Jesse's username, what is it?
JOctopus
Data updated.
Enter a name:

اضغط على CTRL + C عند الانتهاء من اختبار البرنامج للخروج من البرنامج.

يوضح هذا المثال كيف يمكنك تعديل الخرائط بآلية تفاعلية، وستفقد جميع بياناتك بمجرد خروجك من هذا البرنامج باستخدام CTRL + C، إلا إذا خزّنت البيانات في ملف بطريقةٍ ما.

للتلخيص، يمكنك إضافة عناصر إلى الرابطة أو التعديل عليها من خلال الصيغة map[key] = value.

حذف عناصر من الرابطة

مثلما يمكنك إضافة أزواج قيمة-مفتاح إلى الرابطة أو تغيير قيمه، يمكنك أيضًا حذف العناصر الموجودة في الرابطة، فلإزالة زوج قيمة-مفتاح من الرابطة، استخدم الدالة ()delete التي تحتوي على وسيطين، إذ يمثِّل الوسيط الأول الرابطة والثاني المفتاح:

delete(map, key)

لتكن لدينا الرابطة التالية التي تُعبّر عن الأذونات:

permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16:"modify"}

الآن مثلًا لم تعُد بحاجة إلى إذن التعديل، لذا ستُزيله من الرابطة، ثم ستطبع الرابطة لتأكيد إزالتها:

permissions := map[int]string{1: "read", 2: "write", 4: "delete", 8: "create", 16: "modify"}
delete(permissions, 16)
fmt.Println(permissions)

يكون الخرج كما يلي:

map[1:read 2:write 4:delete 8:create]

يزيل السطر (delete(permissions, 16 القيمة المرتبطة بالمفتاح 16 ضمن رابطة الأذونات ثم يُزيل المفتاح نفسه.

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

permissions = map[int]string{}
fmt.Println(permissions)

سيُظهر الخرج أنّ الرابطة قد أصبحت فارغةً:

map[]

بما أن الرابطة هي نوع بيانات قابل للتعديل mutable data type فيمكن التعديل عليها بالإضافة والحذف.

الخاتمة

ألقينا في هذا المقال نظرةً على الخرائط في جو، إذ تتألف الخرائط من أزواج قيمة-مفتاح، وتوفر حلًا ممتازًا لتخزين البيانات دون الحاجة إلى فهرستها، إذ يتيح لنا ذلك استرداد القيم بناءً على معانيها وعلاقتها بأنواع البيانات الأخرى.

ترجمة -وبتصرُّف- للمقال Understanding Maps inGo لصاحبه Gopher Guides.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...