الأسلوب المعتاد: خريطة بقيم فارغة
لا تملك Go كلمة set ولا مجموعة في المكتبة القياسية. الخريطة التي مفاتيحها العناصر وقيمها لا تحمل شيئًا تؤدّي المهمة.
struct{} هو نوع البنية الفارغة وstruct{}{} قيمته الوحيدة. يشغل صفر بايت، فتخزّن الخريطة المفاتيح ولا شيء غيرها.
كل قواعد الخرائط تنطبق: يجب أن تكون المفاتيح قابلة للمقارنة، وترتيب المرور عشوائي، والخريطة nil تسبّب panic عند الكتابة، والكتابات المتزامنة تحتاج قفلًا. تشرح صفحة الخرائط كلًا منها.
map[T]struct{} أم map[T]bool
الكتابة الشائعة الأخرى هي map[T]bool. تُقرأ أفضل لأن المفتاح المفقود يعيد false:
seen := map[string]bool{}
seen["a"] = true
if seen["a"] { ... }
المقابل:
map[T]struct{} | map[T]bool | |
|---|---|---|
| حجم القيمة | 0 بايت | بايت واحد (مع المحاذاة) |
| فحص العضوية | _, ok := s[k] | s[k] |
| الالتباس | لا يوجد | s[k] = false حالة ثالثة |
الصف الثالث هو السبب الحقيقي لتفضيل كثير من قواعد الشيفرة البنية الفارغة: مع bool سيكتب أحدهم يومًا s[k] = false فيتوقّف len(s) عن أن يكون عدد الأعضاء. وللمجموعات الصغيرة لا يهمّ فرق الذاكرة.
إزالة التكرار من شريحة
أشيع استخدام للمجموعة هو إزالة التكرار. يحتفظ هذا بأول ظهور لكل قيمة ويحافظ على الترتيب:
المخرجات:
[b a c]
[3 1 2]
[1 2 3]
الاتحاد والتقاطع والفرق
جبر المجموعات بضع حلقات. مرّ على المجموعة الأصغر عند فحص العضوية في الأخرى، لأن كل بحث يستغرق وقتًا ثابتًا في المتوسّط.
sorted موجودة فقط لجعل المخرجات ثابتة. طباعة مجموعة بالمرور عليها تعطي ترتيبًا مختلفًا في كل تشغيل. تحتاج maps.Keys وslices.Sorted إلى Go 1.23.
النوع المسمّى مثل type set map[string]struct{} يبقى خريطة: تفهرسه وتمرّ عليه بـ range وتحذف منه بـ delete بالطريقة نفسها، ويمكنك تعليق توابع عليه.
نوع Set عام صغير
مع الأنواع العامة (Go 1.18) يغطّي نوع واحد كل أنواع العناصر القابلة للمقارنة. وهذا يكفي معظم البرامج:
تغليف الخريطة في بنية يخفي ضجيج struct{}{} ويضمن أن دالة البناء تنشئ الخريطة، فيزول panic الخريطة nil. لا يمكن أن تكون Sorted تابعًا: لا يستطيع التابع تعريف معاملات نوع خاصة به أو تشديد قيد comparable الخاص بالنوع، والترتيب يحتاج cmp.Ordered. تشرح صفحة الأنواع العامة القيود.
إذا احتجت مجموعة كاملة الميزات (صيغ آمنة للخيوط، وعمليات كثيرة)، توجد حزم خارجية مثل github.com/deckarep/golang-set. لكن في معظم الشيفرات يستخدم مبرمجو Go أسلوب الخريطة أو نوعًا من 30 سطرًا مثل هذا.
مجموعات من البنى
أي نوع قابل للمقارنة يمكن أن يكون عنصرًا، بما في ذلك البنى ذات الحقول القابلة للمقارنة. وهذا يجعل فحوص "هل رأيت هذا الزوج" مباشرة:
type edge struct{ from, to string }
visited := map[edge]struct{}{}
visited[edge{"a", "b"}] = struct{}{}
لا يمكن أن تكون الشرائح والخرائط عناصر مجموعة. لتتبّع الشرائح الفريدة، حوّل كلًا منها إلى مفتاح قابل للمقارنة أولًا، مثل مصفوفة ثابتة الحجم، أو نص مبني بـ fmt.Sprint.
أخطاء شائعة
- نسيان التهيئة.
var s map[string]struct{}قيمتها nil؛ وأول إضافة تسبّب panic. - طباعة مجموعة وتوقّع مخرجات ثابتة. رتّب الأعضاء أولًا.
- استخدام
map[T]boolوتخزينfalse. عندها لا يعدّlenالأعضاء. استخدمdeleteللحذف.
الأسئلة الشائعة
هل تملك Go نوع مجموعة (set)؟
لا. لا توجد مجموعة في المكتبة القياسية. الأسلوب المعتاد خريطة لا تحمل قيمها أي معلومة: map[string]struct{}. الإضافة s[k] = struct{}{}، وفحص العضوية _, ok := s[k]، والحذف delete(s, k)، والحجم len(s).
هل أستخدم map[T]bool أم map[T]struct{} كمجموعة في Go؟
تجعل map[T]struct{} النية صريحة وقيمها تشغل صفر بايت. أما map[T]bool فتُقرأ بشكل أطبع (if seen[x]) لأن المفتاح المفقود يعيد false. كلاهما صحيح؛ والفرق في الذاكرة لا يهمّ إلا في المجموعات الكبيرة جدًا. اختر واحدًا والتزم به.
كيف أزيل التكرار من شريحة في Go؟
للإبقاء على أول ظهور بالترتيب، مرّ في حلقة وتتبّع القيم التي رأيتها في map[T]struct{}، وألحق فقط ما لم تره. وإذا لم يكن الترتيب مهمًا، فرتّب واحذف التكرارات المتجاورة: slices.Sort(s); s = slices.Compact(s).