تمتلك معظم لغات البرمجة نوع بيانات يُمثّل بنية معطيات يُعرف بالقاموس 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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.