"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > الخرائط في الذهاب

الخرائط في الذهاب

تم النشر بتاريخ 2024-08-24
تصفح:366

Maps in Go

مقدمة

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

إعلان الخريطة والتهيئة

للإعلان عن خريطة، يتم ذلك باستخدام قيمة الخريطة [مفتاح]، حيث سيكون المفتاح هو النوع الذي نريد أن يكون عليه مفتاحنا (يجب أن يكون من نوع مشابه https://go.dev/ref/spec#Comparison_operators ) والقيمة ستكون النوع الذي نريد تخزين الخريطة فيه في كل مفتاح، أيًا كان نوعه، من int إلى بنية، أو خريطة أخرى، أيًا ما نريد.

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

إذا أعلنا أنها:

var m map[int]int

قيمته ستكون صفر.

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

m := map[int]int{}

ويمكننا أيضًا تهيئتها تمامًا مثل الشرائح، باستخدام وظيفة الإنشاء.

m := make(map[string]string)

سيؤدي القيام بذلك إلى تهيئة خريطة التجزئة مع تجمع الذاكرة المناسب لها، وبالتالي إرجاع خريطة تشير إلى بنية البيانات تلك.

إضافة وقراءة القيم من الخريطة

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

ages := make(map[string]int)

ages["John"] = 33
ages["Charly"] = 27
ages["Jenny"] = 45
ages["Lisa"] = 19

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

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}

لقراءة القيم، علينا ببساطة الإشارة إلى مفتاح خريطتنا وسيقوم بإرجاع تلك القيمة. على سبيل المثال، لمعرفة عمر ليزا، يمكننا القيام بما يلي:

fmt.Println(ages["Lisa"]) // 19

إذا حاولنا الوصول إلى مفتاح غير موجود، فإن القيمة التي تم الحصول عليها ستكون القيمة الصفرية للنوع، وفي هذه الحالة ستكون ""، لأنها سلسلة.

من أجل التحقق من وجود عنصر في الخريطة، يمكننا التحقق مما إذا كان النوع هو الافتراضي، لكنه غير موثوق به للغاية، لأنه ربما يكون موجودًا ولكن قيمته عبارة عن سلسلة فارغة أو 0 في حالة int ، والتي ستتطابق مع قيمتها الصفرية، لذا يساعدنا Go في ما يلي:

val, ok := ages["Randy"]

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

إذا لم نكن مهتمين بالقيمة ونريد ببساطة التحقق من وجود مفتاح، فيمكننا استخدام _ لتجاهل القيمة كما يلي:

_, ok := ages["Randy"]

كما هو الحال مع المصفوفات والشرائح، يمكننا استخدام الدالة len لمعرفة عدد العناصر الموجودة في الخريطة.

fmt.Println(len(ages)) // 4

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

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

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
agesNew := ages
agesNew["Bryan"] = 77
fmt.Println(agesNew) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]
fmt.Println(ages) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]

إزالة القيم من الخريطة

لحذف عناصر من الخريطة، يوفر لنا Go وظيفة حذف بالتوقيع التالي حذف (m Map[Type]Type1, key Type) الذي يتلقى الخريطة والمفتاح المراد حذفه.
في الحالة السابقة، إذا أردنا إزالة "ليزا" لفعلنا ذلك:

delete(ages, "Lisa")

التكرار عبر الخرائط

إذا أردنا الاطلاع على محتوى الخريطة، فيمكننا القيام بذلك باستخدام for مع تنوع النطاق الذي رأيناه بالفعل في المنشور على المصفوفات والشرائح.
كما هو الحال بعد ذلك، سيكون العنصر الأول هو الفهرس، وبالتالي المفتاح، والثاني القيمة.

for key, value := range ages {
    fmt.Printf("%s: %d\n", key, value)
}

// Output:
// Jenny: 45
// Lisa: 19
// John: 33
// Charly: 27

كما هو الحال مع المصفوفات والشرائح، إذا كنا مهتمين فقط بالقيمة، بدون المفتاح، يمكننا حذفها باستخدام _.

for _, value := range ages {
    fmt.Println(value)
}

// Output:
// 19
// 33
// 27
// 45

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

for key := range ages {
    fmt.Println(key)
}

// Output:
// John
// Charly
// Jenny
// Lisa

فرز الخريطة

كما ذكرت في المقدمة، المعلومات غير مرتبة في الخريطة، لذا عند المرور عبرها لا يمكننا تحديد الترتيب الذي تتبعه، ولا يمكن لـ Go ضمان أن الترتيب بين عمليات التنفيذ هو نفسه.
كما رأينا مع المصفوفات والشرائح، توجد في المكتبة القياسية حزمة فرز تساعدنا في فرز العناصر: https://pkg.go.dev/sort

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

ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
keys := make([]string, 0, len(ages))
for k := range ages {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, ages[k])
}

// Output:
// Charly 27
// Jenny 45
// John 33
// Lisa 19

نعلن عن خريطة الأعمار الخاصة بنا بالإعلان القصير كما رأينا من قبل.
نقوم بإنشاء شرائح سلسلة لتخزين المفاتيح واستخدام طريقة الصنع بطول 0، نظرًا لأنه ليس لدينا أي مفاتيح في الوقت الحالي، لكننا نحتفظ بالسعة التي ستتمتع بها باستخدام طريقة len لطول خريطتنا.
نمر عبر خريطة الأعمار للاحتفاظ بمفاتيحها وإضافتها إلى الشريحة التي تم إنشاؤها.
نقوم بفرز المفاتيح أبجديًا باستخدام وظيفةsort.Strings.
نمر عبر شريحة المفاتيح، التي تم طلبها بالفعل، ونصل إلى الخريطة بالمفتاح المعني.
بهذه الطريقة سوف نصل إلى الخريطة بطريقة منظمة ويمكننا القيام بالمنطق الذي يحتاجه برنامجنا.

مشاكل مع التزامن

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

إذا استخدمنا حزمة المزامنة: https://pkg.go.dev/sync من المكتبة القياسية، فيمكننا التحكم في التزامن بين goroutines المختلفة.
الاستخدام المحتمل هو نوع RWMutex الذي يسمح لنا بقفل وفتح عمليات القراءة والكتابة للنوع. لذا، إذا كان لدينا نوع يحتوي على sync.RWMutex وخريطة يمكننا التحكم في وقت الوصول إليها.
هناك نوع آخر مثير للاهتمام يجب التحقيق فيه ضمن حزمة المزامنة نفسها وهو Map، الذي يقدم لنا بالفعل سلسلة من الوظائف التي ستساعدنا في العمل مع خريطتنا، والتي في النهاية لن نتمكن من العمل معها محليًا، كما هو الحال مع الحل السابق.
اعتمادًا على حالة الاستخدام التي ننفذها، سيكون أحدهما أكثر فائدة لنا، ولا يوجد أحد أفضل من الآخر، وسيعتمد دائمًا على ما نحتاج إليه.

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

بيان الافراج تم نشر هذه المقالة على: https://dev.to/charly3pins/maps-in-go-5a3j?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ [email protected] لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3