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) |
2. الصياغة والاستخدام الأساسي
إنشاء Map
// الطريقة 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 // ثم الإسناد
var m map[K]V هي nil — الكتابة إليها ستُسبب panic، لكن القراءة منها (تُعيد قيمة صفرية) و len() (تُعيد 0) يعملان بشكل طبيعي.
عمليات CRUD
m := map[string]int{"a": 1, "b": 2}
// إنشاء
m["c"] = 3
// تحديث
m["a"] = 10
// قراءة
v := m["a"] // v = 10
// حذف
delete(m, "b")
نمط comma ok
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.
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 قد يختلف):
درجة Alice: 90
Bob: 88
Alice: 90
عدد الطلاب: 2
مثال 2: الاستخدام المتوسط (الصعوبة ⭐⭐)
استعلام آمن بنمط comma ok، iterate على maps، و maps متداخلة.
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)
}
}
}
المخرجات:
apple لديه 5
grape غير موجود، إضافة إلى القائمة
جميع الفواكه:
apple: 5
banana: 3
grape: 10
درجة Alice في الفصل A: 90
جميع درجات الفصول:
الفصل A:
Alice: 90
Bob: 85
الفصل B:
Charlie: 92
Diana: 88
مثال 3: التطبيق الشامل (الصعوبة ⭐⭐⭐)
تنفيذ عداد تكرار الكلمات باستخدام معالجة بيانات قائمة على map (إخراج مُرتّب، إيجاد الكلمات عالية التكرار).
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))
}
مثال المخرجات:
تكرار كلمات النص 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 لتنفيذ ذاكرة مؤقتة بسيطة في الذاكرة لتجنب الحساب المُتكرر.
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 لتجميع وعد البيانات.
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)
}
}
المخرجات:
الفصل A (3 أشخاص): [Alice Charlie Eve]
الفصل B (2 أشخاص): [Bob Diana]
❓ أسئلة شائعة
س1: لماذا ترتيب iterate على map مختلف في كل مرة؟
Go تُعمّد ت randomize ترتيب iterate على map. هذا يمنع المطورين من الاعتماد على ترتيب iterate، لأن البنية الداخلية قد تتغير في سيناريوهات التزامن. إذا كنت بحاجة إلى iterate مُرتّب، اجمع المفاتيح في slice أولاً، ارتبها، ثم iterate:
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. الحلول:
- استخدم
sync.Mutexأوsync.RWMutexلحماية map - استخدم
sync.Mapالمتوفرة منذ Go 1.9 (مناسبة لسيناريوهات قراءة كثيفة، كتابة خفيفة)
// استخدام sync.Map
var m sync.Map
m.Store("key", "value")
v, ok := m.Load("key")
س3: أي أنواع يمكن استخدامها كمفاتيح map؟
مفاتيح Map يجب أن تكون أنواع قابلة للمقارنة، تشمل:
- الأنواع الأساسية:
int،float64،string،bool - المؤشرات
- المصفوفات (بعناصر قابلة للمقارنة)
- الهياكل (بحقول قابلة للمقارنة)
أنواع لا يمكن أن تكون مفاتيح: slice، map، func.
س4: كيف أتحقق ما إذا كانت map تحتوي على مفتاح معين؟
استخدم نمط comma ok:
if _, ok := m[key]; ok {
// المفتاح موجود
} else {
// المفتاح غير موجود
}
لا تتحقق من وجود مفتاح عبر اختبار ما إذا كانت القيمة صفرية، لأن القيمة الصفرية قد تكون قيمة صالحة.
📖 ملخص
- map هي بنية بيانات مفتاح-قيمة المدمجة في Go؛ تبحث بسرعة عن قيم بمفاتيح
- تُنشأ بـ
makeأو حرفيmap[K]V{}؛ map الفارغةnilالمُعلنة بـvarللقراءة فقط - إضافة/تحديث:
m[key] = value؛ قراءة:v := m[key]؛ حذف:delete(m, key) - استخدم نمط comma ok (
v, ok := m[key]) للتحقق الآمن من وجود مفتاح - استخدم
for k, v := range mللiterate؛ الترتيب عشوائي وغير موثوق - map نوع مرجع؛ الإسناد وتمرير المعاملات لا ينسخان البيانات
- map غير آمنة للتزامن؛ استخدم أقفالاً أو
sync.Mapلقراءة/كتابة متعددة goroutine - map مقابل slice: استخدم map للبحث المتكرر، slice للتخزين المُرتّب؛ الاثنان غالباً ما يُستخدمان معاً
📝 تمارين
تمرين 1 (⭐)
أنشئ map لتخزين 5 لغات برمجة وسنوات اختراعها، ثم:
- أضف لغتين جديدتين
- عدّل سنة إحدى اللغات
- احذف لغة واحدة
- استخدم نمط comma ok للتحقق من وجود لغة
- Iterate واطبع جميع المحتويات
تمرين 2 (⭐⭐)
نفذ برنامج جهات اتصال بسيط:
- عرّف
map[string][]string، حيث المفتاح اسم جهة الاتصال والقيمة قائمة أرقام الهواتف - نفّذ الدوال: إضافة جهة اتصال، إضافة رقم هاتف، البحث عن جهة اتصال، حذف جهة اتصال
- نفّذ عرض جميع جهات الاتصال مُجمّعة حسب الحرف الأول
// مثال المخرجات المتوقعة:
// A: Alice - [13800001111, 13900002222]
// B: Bob - [13700003333]
تمرين 3 (⭐⭐⭐)
نفذ نظام إدارة درجات الطلاب:
- استخدم map متداخلة
map[string]map[string]float64(فصل → طالب → درجة) - نفّذ الدوال: إضافة درجة، استعلام جميع درجات mater لطالب، حساب متوسط الفصل
- نفّذ دالة: إيجاد الطالب الحاصل على أعلى درجة في كل mater عبر جميع الفصول
- أخرج النتائج كجداول مُنسقة
// مثال المخرجات المتوقعة:
// ========== متوسطات الفصول ==========
// الفصل A: 87.5
// الفصل B: 91.2
//
// ========== أعلى الدرجات حسب المادة ==========
// الرياضيات: Alice (98.0)
// اللغة الإنجليزية: Bob (95.0)



