Map

Map

تخيّل أنك تبحث في قاموس: تُدخل كلمة (مفتاح) ويمكنك فوراً إيجاد تعريفها (قيمة). لا تحتاج أن تتصفح من الصفحة الأولى إلى الأخيرة — تذهب مباشرة إلى الصفحة المستهدفة. map في Go هي بالضبط "قاموس" من هذا النوع — هي بنية بيانات مفتاح-قيمة تتيح لك البحث السريع عن قيمة بمفتاح بتعقيد زمني O(1).


1. المفاهيم الأساسية

المفهوم الوصف
التعريف map[KeyType]ValueType؛ KeyType يجب أن يكون نوعاً قابلاً للمقارنة (لا يمكن أن يكون slice أو map أو func)
الإنشاء make(map[K]V) أو map[K]V{} حرفي
إضافة/تحديث m[key] = value (يُضيف إذا لم يوجد المفتاح، يُكتب فوقه إذا وجد)
قراءة v := m[key]
حذف delete(m, key)
نمط comma ok v, ok := m[key]؛ ok يكون false عندما لا يوجد المفتاح
iterate for k, v := range m (ترتيب عشوائي، غير موثوق)
الطول len(m)
⚠️ ملاحظة: map نوع مرجع؛ الإسناد وتمرير المعاملات يمرران المرجع، وليس نسخة من البيانات.


2. الصياغة والاستخدام الأساسي

إنشاء Map

GO
// الطريقة 1: استخدام make
m1 := make(map[string]int)

// الطريقة 2: إنشاء حرفي مع تهيئة
m2 := map[string]int{
    "apple":  5,
    "banana": 3,
}

// الطريقة 3: إعلان ثم إسناد
var m3 map[string]int        // m3 فارغة nil هنا، لا يمكن الإسناد مباشرة
m3 = make(map[string]int)    // التهيئة أولاً
m3["cherry"] = 7             // ثم الإسناد
💡 نصيحة: map المُعلنة بـ var m map[K]V هي nilالكتابة إليها ستُسبب panic، لكن القراءة منها (تُعيد قيمة صفرية) و len() (تُعيد 0) يعملان بشكل طبيعي.

عمليات CRUD

GO
m := map[string]int{"a": 1, "b": 2}

// إنشاء
m["c"] = 3

// تحديث
m["a"] = 10

// قراءة
v := m["a"]  // v = 10

// حذف
delete(m, "b")
💡 نصيحة: حذف مفتاح غير موجود لن يُسبب خطأ؛ لا يحدث شيء.

نمط comma ok

GO
m := map[string]int{"x": 42}

v, ok := m["x"]  // v = 42، ok = true
v, ok = m["y"]   // v = 0، ok = false (تُعيد القيمة الصفرية لـ int)

if _, exists := m["z"]; !exists {
    fmt.Println("المفتاح 'z' غير موجود")
}
💡 نصيحة: إذا لم تكترث بالقيمة، استخدم _ لتجاهلها: _, ok := m[key].


3. أمثلة الكود

مثال 1: الاستخدام الأساسي (الصعوبة ⭐)

إنشاء جدول درجات طالب وإجراء عمليات CRUD.

GO
package main

import "fmt"

func main() {
    // إنشاء map لدرجات الطلاب
    scores := map[string]int{
        "Alice": 90,
        "Bob":   85,
    }

    // إضافة طالب
    scores["Charlie"] = 92

    // تعديل درجة Bob
    scores["Bob"] = 88

    // الاستعلام عن درجة Alice
    fmt.Println("درجة Alice:", scores["Alice"])

    // حذف Charlie
    delete(scores, "Charlie")

    // iterate على جميع درجات الطلاب
    for name, score := range scores {
        fmt.Printf("%s: %d\n", name, score)
    }

    fmt.Println("عدد الطلاب:", len(scores))
}
▶ جرّب الكود

مثال المخرجات (ترتيب iterate قد يختلف):

TEXT
درجة Alice: 90
Bob: 88
Alice: 90
عدد الطلاب: 2

مثال 2: الاستخدام المتوسط (الصعوبة ⭐⭐)

استعلام آمن بنمط comma ok، iterate على maps، و maps متداخلة.

GO
package main

import "fmt"

func main() {
    // ========== نمط comma ok ==========
    fruits := map[string]int{
        "apple":  5,
        "banana": 3,
    }

    // استعلام آمن
    if count, ok := fruits["apple"]; ok {
        fmt.Printf("apple لديه %d\n", count)
    }

    if count, ok := fruits["grape"]; !ok {
        fmt.Println("grape غير موجود، إضافة إلى القائمة")
        fruits["grape"] = 10
    }

    // ========== iterate بـ range ==========
    fmt.Println("\nجميع الفواكه:")
    for fruit, count := range fruits {
        fmt.Printf("  %s: %d\n", fruit, count)
    }

    // ========== map متداخلة (map من maps) ==========
    // الفصل → الطالب → الدرجة
    classScores := map[string]map[string]int{
        "الفصل A": {
            "Alice": 90,
            "Bob":   85,
        },
        "الفصل B": {
            "Charlie": 92,
            "Diana":   88,
        },
    }

    // استعلام map متداخلة
    if class, ok := classScores["الفصل A"]; ok {
        if score, ok := class["Alice"]; ok {
            fmt.Printf("\nدرجة Alice في الفصل A: %d\n", score)
        }
    }

    // iterate على map متداخلة
    fmt.Println("\nجميع درجات الفصول:")
    for class, students := range classScores {
        fmt.Printf("  %s:\n", class)
        for name, score := range students {
            fmt.Printf("    %s: %d\n", name, score)
        }
    }
}
▶ جرّب الكود

المخرجات:

TEXT
apple لديه 5
grape غير موجود، إضافة إلى القائمة

جميع الفواكه:
  apple: 5
  banana: 3
  grape: 10

درجة Alice في الفصل A: 90

جميع درجات الفصول:
  الفصل A:
    Alice: 90
    Bob: 85
  الفصل B:
    Charlie: 92
    Diana: 88

مثال 3: التطبيق الشامل (الصعوبة ⭐⭐⭐)

تنفيذ عداد تكرار الكلمات باستخدام معالجة بيانات قائمة على map (إخراج مُرتّب، إيجاد الكلمات عالية التكرار).

GO
package main

import (
    "fmt"
    "sort"
    "strings"
)

// WordCounter يُعدّ تكرار كل كلمة في نص
func WordCounter(text string) map[string]int {
    // تحويل إلى أحرف صغيرة والتقسيم بفراغات
    words := strings.Fields(strings.ToLower(text))

    // إنشاء map لتكرار الكلمات
    freq := make(map[string]int)

    for _, word := range words {
        // إزالة علامات الترقيم (معالجة بسيطة)
        word = strings.Trim(word, ".,!?;:\"'")
        if word != "" {
            freq[word]++
        }
    }

    return freq
}

// TopN يُعيد أعلى N كلمة تكراراً (تنازلي حسب التكرار)
func TopN(freq map[string]int, n int) []string {
    // تحويل map إلى slice قابل للترتيب
    type wordFreq struct {
        word  string
        count int
    }

    // بناء slice
    pairs := make([]wordFreq, 0, len(freq))
    for w, c := range freq {
        pairs = append(pairs, wordFreq{w, c})
    }

    // ترتيب تنازلي حسب التكرار
    sort.Slice(pairs, func(i, j int) bool {
        if pairs[i].count == pairs[j].count {
            return pairs[i].word < pairs[j].word // أبجدي عندما يكون التكرار متماثلاً
        }
        return pairs[i].count > pairs[j].count
    })

    // أخذ أعلى N
    if n > len(pairs) {
        n = len(pairs)
    }

    result := make([]string, n)
    for i := 0; i < n; i++ {
        result[i] = fmt.Sprintf("%s(%d)", pairs[i].word, pairs[i].count)
    }

    return result
}

// MergeFreq يدمج mapتين لتكرار الكلمات
func MergeFreq(a, b map[string]int) map[string]int {
    result := make(map[string]int)

    // نسخ بيانات a
    for k, v := range a {
        result[k] = v
    }

    // تراكم بيانات b
    for k, v := range b {
        result[k] += v
    }

    return result
}

func main() {
    text1 := "Go is great. Go is fast. Go is easy to learn."
    text2 := "I love Go. Go makes programming fun and easy."

    // عد تكرار الكلمات
    freq1 := WordCounter(text1)
    freq2 := WordCounter(text2)

    fmt.Println("تكرار كلمات النص 1:")
    for word, count := range freq1 {
        fmt.Printf("  %s: %d\n", word, count)
    }

    fmt.Println("\nتكرار كلمات النص 2:")
    for word, count := range freq2 {
        fmt.Printf("  %s: %d\n", word, count)
    }

    // دمج تكرارات الكلمات
    merged := MergeFreq(freq1, freq2)

    // إيجاد أعلى 5 كلمات تكراراً
    fmt.Println("\nأعلى 5 كلمات عالية التكرار بعد الدمج:")
    top5 := TopN(merged, 5)
    for i, item := range top5 {
        fmt.Printf("  %d. %s\n", i+1, item)
    }

    // عد إجمالي الكلمات
    totalWords := 0
    for _, count := range merged {
        totalWords += count
    }
    fmt.Printf("\nإجمالي الكلمات: %d، الكلمات الفريدة: %d\n", totalWords, len(merged))
}
▶ جرّب الكود

مثال المخرجات:

TEXT
تكرار كلمات النص 1:
  go: 3
  is: 3
  great: 1
  fast: 1
  easy: 1
  to: 1
  learn: 1

تكرار كلمات النص 2:
  i: 1
  love: 1
  go: 2
  makes: 1
  programming: 1
  fun: 1
  and: 1
  easy: 1

أعلى 5 كلمات عالية التكرار بعد الدمج:
  1. go(5)
  2. is(3)
  3. easy(2)
  4. and(1)
  5. fast(1)

إجمالي الكلمات: 18، الكلمات الفريدة: 12

3. حالات استخدام شائعة

الحالة 1: ذاكرة مؤقتة / بحث سريع

استخدم map لتنفيذ ذاكرة مؤقتة بسيطة في الذاكرة لتجنب الحساب المُتكرر.

GO
package main

import "fmt"

// متتالية فيبوناتشي (مع ذاكرة مؤقتة)
var cache = map[int]int{0: 0, 1: 1}

func fib(n int) int {
    // تحقق من الذاكرة المؤقتة أولاً
    if val, ok := cache[n]; ok {
        return val
    }

    // فشل الذاكرة المؤقتة، احسب وخزن في الذاكرة المؤقتة
    result := fib(n-1) + fib(n-2)
    cache[n] = result
    return result
}

func main() {
    for i := 0; i <= 10; i++ {
        fmt.Printf("fib(%d) = %d\n", i, fib(i))
    }
    fmt.Println("\nمحتويات الذاكرة المؤقتة:", cache)
}

الحالة 2: إحصائيات مُجمّعة

استخدم map لتجميع وعد البيانات.

GO
package main

import "fmt"

func main() {
    // قائمة الطلاب: اسم → فصل
    students := map[string]string{
        "Alice":   "الفصل A",
        "Bob":     "الفصل B",
        "Charlie": "الفصل A",
        "Diana":   "الفصل B",
        "Eve":     "الفصل A",
    }

    // تجميع حسب الفصل
    groups := make(map[string][]string)
    for name, class := range students {
        groups[class] = append(groups[class], name)
    }

    // طباعة النتائج المُجمّعة
    for class, members := range groups {
        fmt.Printf("%s (%d أشخاص): %v\n", class, len(members), members)
    }
}

المخرجات:

TEXT
الفصل A (3 أشخاص): [Alice Charlie Eve]
الفصل B (2 أشخاص): [Bob Diana]

❓ أسئلة شائعة

س1: لماذا ترتيب iterate على map مختلف في كل مرة؟

Go تُعمّد ت randomize ترتيب iterate على map. هذا يمنع المطورين من الاعتماد على ترتيب iterate، لأن البنية الداخلية قد تتغير في سيناريوهات التزامن. إذا كنت بحاجة إلى iterate مُرتّب، اجمع المفاتيح في slice أولاً، ارتبها، ثم iterate:

GO
keys := make([]string, 0, len(m))
for k := range m {
    keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
    fmt.Println(k, m[k])
}

س2: هل map آمنة للخيوط؟

لا. map في Go ليست آمنة للتزامن؛ عدة goroutines تقرأ وتكتب نفس map في نفس الوقت ستُسبب panic. الحلول:

GO
// استخدام sync.Map
var m sync.Map
m.Store("key", "value")
v, ok := m.Load("key")

س3: أي أنواع يمكن استخدامها كمفاتيح map؟

مفاتيح Map يجب أن تكون أنواع قابلة للمقارنة، تشمل:

أنواع لا يمكن أن تكون مفاتيح: slice، map، func.

س4: كيف أتحقق ما إذا كانت map تحتوي على مفتاح معين؟

استخدم نمط comma ok:

GO
if _, ok := m[key]; ok {
    // المفتاح موجود
} else {
    // المفتاح غير موجود
}

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


📖 ملخص


📝 تمارين

تمرين 1 (⭐)

أنشئ map لتخزين 5 لغات برمجة وسنوات اختراعها، ثم:

  1. أضف لغتين جديدتين
  2. عدّل سنة إحدى اللغات
  3. احذف لغة واحدة
  4. استخدم نمط comma ok للتحقق من وجود لغة
  5. Iterate واطبع جميع المحتويات

تمرين 2 (⭐⭐)

نفذ برنامج جهات اتصال بسيط:

  1. عرّف map[string][]string، حيث المفتاح اسم جهة الاتصال والقيمة قائمة أرقام الهواتف
  2. نفّذ الدوال: إضافة جهة اتصال، إضافة رقم هاتف، البحث عن جهة اتصال، حذف جهة اتصال
  3. نفّذ عرض جميع جهات الاتصال مُجمّعة حسب الحرف الأول
GO
// مثال المخرجات المتوقعة:
// A: Alice - [13800001111, 13900002222]
// B: Bob - [13700003333]

تمرين 3 (⭐⭐⭐)

نفذ نظام إدارة درجات الطلاب:

  1. استخدم map متداخلة map[string]map[string]float64 (فصل → طالب → درجة)
  2. نفّذ الدوال: إضافة درجة، استعلام جميع درجات mater لطالب، حساب متوسط الفصل
  3. نفّذ دالة: إيجاد الطالب الحاصل على أعلى درجة في كل mater عبر جميع الفصول
  4. أخرج النتائج كجداول مُنسقة
GO
// مثال المخرجات المتوقعة:
// ========== متوسطات الفصول ==========
// الفصل A: 87.5
// الفصل B: 91.2
//
// ========== أعلى الدرجات حسب المادة ==========
// الرياضيات: Alice (98.0)
// اللغة الإنجليزية: Bob (95.0)

الدرس التالي

07-struct - الهياكل

Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%