فرز الهياكل في Go: الطريق المختصر والسحر تحت الغطاء

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

فرز الهياكل في Go: كيف تكتب باختصار ولا تكسر عقلك

\n\n

تخيل: لديك شريحة من الهياكل، وتحتاج إلى فرزها حسب أي حقل. الاسم، العمر، الراتب، تاريخ الميلاد — أي شيء. على Stack Overflow السؤال «What is the shortest way to simply sort an array of structs by (arbitrary) field names?» حصل على آلاف المشاهدات. وهذا ليس من قبيل الصدفة. في Go لا توجد طريقة «سحرية» مدمجة مثل list.sort(key=lambda x: x.field) في Python. لكن هناك طريقة أنيقة وقوية — sort.Slice. في هذه المقالة لن نعرض الكود فقط، بل سننظر تحت الغطاء: كيف يعمل الفرز، ماذا يفعل وقت التشغيل وكيف تتجنب المشاكل النموذجية.

\n\n

المشكلة: «لماذا لا يمكنك ببساطة كتابة sort() في Go؟»

\n\n

إذا كنت قادماً من Python أو JavaScript أو Ruby، فأنت معتاد على سطر واحد:

\n
users.sort(key=lambda u: u.age)
\n

في Go لا يوجد هذا الرفاهية. الكتابة القوية وغياب الأدوية العامة (حتى Go 1.18) أجبرتك على كتابة تطبيق منفصل لـ sort.Interface لكل نوع هيكل. كان هذا مرهقاً. على سبيل المثال:

\n
type ByAge []User\nfunc (a ByAge) Len() int           { return len(a) }\nfunc (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }\nfunc (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }\n\nfunc main() {\n    users := []User{{"Alice", 30}, {"Bob", 25}}\n    sort.Sort(ByAge(users))\n}
\n

يعمل، لكن لكل حقل — نوع جديد. وإذا كان لديك 10 حقول؟ يتحول الكود إلى ورقة طويلة. تريد شيئاً أكثر إحكاما.

\n\n

الحل: sort.Slice — مجموعتك الأنيقة

\n\n

بدءاً من Go 1.8، ظهرت في حزمة sort دالة Slice. تأخذ شريحة ودالة مجهولة (إغلاق) تحدد الترتيب. ها هي — أقصر طريقة:

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Age < users[j].Age\n})
\n

كل شيء. ثلاثة أسطر. بدون تعريف أنواع جديدة، بدون دوال إضافية. تريد الفرز حسب الاسم؟ نغير الحقل:

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Name < users[j].Name\n})
\n

وإذا كنت بحاجة إلى ترتيب تنازلي؟ ببساطة نغير الإشارة:

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Age > users[j].Age // الآن تنازلياً\n})
\n

جميل. لكن كيف يعمل هذا؟ وما هي الخدعة؟

\n\n

تحت الغطاء: sort.Slice والدوال المجهولة

\n\n

دعنا نحلل ما يحدث عندما تستدعي sort.Slice. الدالة معرفة هكذا:

\n
func Slice(slice interface{}, less func(i, j int) bool)
\n

أول ما يلفت الانتباه هو interface{}. الدالة تقبل أي شريحة. كيف تعرف طولها وكيف تعيد ترتيب العناصر؟ الإجابة — عبر reflection (حزمة reflect).

\n

داخل sort.Slice تقوم بما يلي:

\n
    \n
  1. تتحقق من أن ما تم تمريره هو شريحة. إذا مررت شيئاً ليس شريحة (مثل مصفوفة أو هيكل)، سيحدث ذعر (panic) في وقت التنفيذ.
  2. \n
  3. تستخرج الطول عبر reflect.ValueOf(slice).Len().
  4. \n
  5. تنشئ كائنًا مساعدًا ينفذ الواجهة sort.Interface ديناميكياً. هذا الكائن يخزن مرجعاً للشريحة الأصلية (عبر reflection) ودالتك less.
  6. \n
  7. تشغل خوارزمية الفرز القياسية (quicksort من حزمة sort). عندما تحتاج الخوارزمية لمقارنة عنصرين، تستدعي دالتك less. عندما تحتاج لتبديل الأماكن — تستخدم reflect.Swapper(slice).
  8. \n
\n

النقطة الأساسية: دالتك less تلتقط الشريحة الأصلية من السياق الخارجي. هذا إغلاق (closure). المترجم يضع المتغير users في الكومة (heap) ليكون متاحاً حتى بعد الخروج من الدالة m

المدونات

توصيات الكتب