Go में स्ट्रक्चर की सॉर्टिंग: छोटा रास्ता और अंदर का जादू

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

Go में स्ट्रक्चर की सॉर्टिंग: कैसे छोटा लिखें और अपना दिमाग न खराब करें

\n\n

कल्पना करें: आपके पास स्ट्रक्चर का एक स्लाइस है, और आपको इसे किसी भी फ़ील्ड के अनुसार सॉर्ट करना है। नाम, उम्र, वेतन, जन्म तिथि — कुछ भी। Stack Overflow पर प्रश्न «What is the shortest way to simply sort an array of structs by (arbitrary) field names?» ने हज़ारों व्यूज़ जुटाए। और यह कोई संयोग नहीं है। Go में Python के list.sort(key=lambda x: x.field) जैसा कोई अंतर्निहित «जादुई» तरीका नहीं है। लेकिन एक सुंदर और शक्तिशाली तरीका है — sort.Slice। इस लेख में हम सिर्फ कोड नहीं दिखाएंगे, बल्कि अंदर की बात भी देखेंगे: सॉर्टिंग कैसे काम करती है, रनटाइम क्या करता है और सामान्य गलतियों से कैसे बचें।

\n\n

समस्या: «Go में सिर्फ sort() क्यों नहीं लिख सकते?»

\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. जाँचता है कि स्लाइस पास किया गया है। अगर स्लाइस नहीं (जैसे, ऐरे या स्ट्रक्चर) पास किया जाता है, तो प्रोग्राम रनटाइम पर पैनिक करेगा।
  2. \n
  3. लंबाई निकालता है reflect.ValueOf(slice).Len() के माध्यम से।
  4. \n
  5. एक सहायक ऑब्जेक्ट बनाता है जो गतिशील रूप से sort.Interface इंटरफ़ेस को लागू करता है। यह ऑब्जेक्ट मूल स्लाइस का संदर्भ (reflection के माध्यम से) और आपके less फ़ंक्शन को संग्रहीत करता है।
  6. \n
  7. मानक सॉर्टिंग एल्गोरिदम चलाता है (पैकेज sort से quicksort)। जब एल्गोरिदम को दो तत्वों की तुलना करनी होती है, तो यह आपके less को कॉल करता है। जब स्थान बदलने की आवश्यकता होती है — तो यह reflect.Swapper(slice) का उपयोग करता है।
  8. \n
\n

मुख्य बिंदु: आपका less फ़ंक्शन बाहरी संदर्भ से मूल स्लाइस को कैप्चर करता है। यह एक क्लोज़र (closure) है। कंपाइलर वेरिएबल users को हीप पर रखता है, ताकि यह फ़ंक्शन m से बाहर निकलने के बाद भी उपलब्ध रहे।

ब्लॉग

पुस्तक अनुशंसाएँ