1 مقدمة في الخرائط

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

الميزات

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

حالات الاستخدام

  • الإحصائيات: عد العناصر غير المتكررة بسرعة باستخدام فرادة المفاتيح.
  • التخزين الم

3.2 التحقق من وجود المفتاح

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

func main() {
    scores := map[string]int{
        "Alice": 92,
        "Bob": 85,
    }

    // التحقق مما إذا كان المفتاح "Bob" موجودًا
    score, exists := scores["Bob"]
    if exists {
        fmt.Println("درجة بوب:", score)
    } else {
        fmt.Println("لم يتم العثور على درجة بوب.")
    }

    // التحقق مما إذا كان المفتاح "Charlie" موجودًا
    _, exists = scores["Charlie"]
    if exists {
        fmt.Println("تم العثور على درجة تشارلي.")
    } else {
        fmt.Println("لم يتم العثور على درجة تشارلي.")
    }
}

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

3.3 إضافة وتحديث العناصر

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

func main() {
    // تحديد خريطة فارغة
    scores := make(map[string]int)

    // إضافة العناصر
    scores["Alice"] = 92
    scores["Bob"] = 85

    // تحديث العناصر
    scores["Alice"] = 96  // تحديث مفتاح موجود بالفعل

    // طباعة الخريطة
    fmt.Println(scores)   // الناتج: map[Alice:96 Bob:85]
}

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

3.4 حذف العناصر

يمكن إزالة العناصر من الخريطة باستخدام دالة delete المدمجة. يوضح المثال التالي عملية الحذف:

func main() {
    scores := map[string]int{
        "Alice": 92,
        "Bob": 85,
        "Charlie": 78,
    }

    // حذف عنصر
    delete(scores, "Charlie")

    // طباعة الخريطة للتأكد من حذف تشارلي
    fmt.Println(scores)  // الناتج: map[Alice:92 Bob:85]
}

تأخذ دالة delete معها معلمتين، الخريطة نفسها كمعلمة أولى والمفتاح الذي سيتم حذفه كمعلمة ثانية. إذا لم يكن المفتاح موجودًا في الخريطة، فإن دالة delete لن تكون لها أي تأثير ولن تُسفر عن خطأ.

4 تجوال الخريطة

في لغة Go، يمكنك استخدام عبارة for range لتجوال هيكل البيانات الخريطة والوصول إلى كل زوج مفتاح-قيمة في الحاوية. هذا النوع من عملية التجوال بواسطة الحلقة هو عملية أساسية مدعومة من قبل هيكل البيانات الخريطة.

4.1 استخدام for range للتكرار عبر الخريطة

يمكن استخدام عبارة for range مباشرة على الخريطة لاسترجاع كل زوج مفتاح-قيمة في الخريطة. فيما يلي مثال أساسي على استخدام for range للتكرار عبر الخريطة:

package main

import "fmt"

func main() {
    myMap := map[string]int{"Alice": 23, "Bob": 25, "Charlie": 28}

    for key, value := range myMap {
        fmt.Printf("المفتاح: %s، القيمة: %d\n", key, value)
    }
}

في هذا المثال، يتم تعيين المتغير key بالمفتاح الحالي في الدورة، ويتم تعيين المتغير value بالقيمة المرتبطة بهذا المفتاح.

4.2 الاعتبارات المتعلقة بترتيب التكرار

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

على سبيل المثال، قد يؤدي تشغيل الكود التالي مرتين على التوالي إلى إخراج مختلف:

package main

import "fmt"

func main() {
    myMap := map[string]int{"Alice": 23, "Bob": 25, "Charlie": 28}

    fmt.Println("التكرار الأول:")
    for key, value := range myMap {
        fmt.Printf("المفتاح: %s، القيمة: %d\n", key, value)
    }

    fmt.Println("\nالتكرار الثاني:")
    for key, value := range myMap {
        fmt.Printf("المفتاح: %s، القيمة: %d\n", key, value)
    }
}

5 مواضيع متقدمة حول الخرائط

فيما يلي، سنقدم عدة مواضيع متقدمة تتعلق بالخرائط، والتي يمكن أن تساعدك في فهم الخرائط بشكل أفضل والاستفادة منها بشكل أكبر.

5.1 الذاكرة وأداء الخرائط

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

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

myMap := make(map[string]int, 100)

يمكن أن يقلل ذلك من الزيادة الزائدة في توسيع الخريطة أثناء التشغيل.

5.2 خصائص نوع الإشارة للخرائط

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

إليك مثال:

package main

import "fmt"

func main() {
    originalMap := map[string]int{"Alice": 23, "Bob": 25}
    newMap := originalMap

    newMap["Charlie"] = 28

    fmt.Println(originalMap) // الإخراج سيظهر زوج المفتاح-القيمة الجديد "Charlie": 28
}

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

5.3 سلامة التنفيذ وsync.Map

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

توفر مكتبة Go القياسية نوع 'sync.Map'، وهو خريطة آمنة مصممة للبيئات المتزامنة. يقدم هذا النوع أساليب أساسية مثل Load، Store، LoadOrStore، Delete، وRange للعمل على الخريطة.

أدناه مثال على استخدام 'sync.Map':

package main

import (
    "fmt"
    "sync"
)

func main() {
    var mySyncMap sync.Map

    // تخزين أزواج المفتاح-القيمة
    mySyncMap.Store("Alice", 23)
    mySyncMap.Store("Bob", 25)

    // استرداد وطباعة زوج المفتاح-القيمة
    if value, ok := mySyncMap.Load("Alice"); ok {
        fmt.Printf("المفتاح: Alice، القيمة: %d\n", value)
    }

    // استخدام الأسلوب "Range" للانتقال عبر sync.Map
    mySyncMap.Range(func(key, value interface{}) bool {
        fmt.Printf("المفتاح: %v، القيمة: %v\n", key, value)
        return true // متابعة التكرار
    })
}

باستخدام 'sync.Map' بدلاً من الخريطة العادية، يمكن تجنب مشكلات شرط السباق عند تعديل الخريطة في بيئة متزامنة، مما يضمن السلامة في التداول.