Menu

מיון slice ב-Golang: slices.Sort, SortFunc וכמה שדות

מיינו slices ב-Go עם slices.Sort ו-slices.SortFunc, מיינו structs לפי שדה אחד או כמה שדות עם cmp.Compare, שמרו על הסדר של איברים שווים עם מיון יציב, וקראו קוד ישן שמשתמש ב-sort.Slice.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

מיון טיפוסים מובנים

slices.Sort ממיינת כל slice שטיפוס האיבר שלו ניתן לסידור: מספרים שלמים, מספרים עשרוניים ומחרוזות. היא ממיינת במקום ולא מחזירה כלום.

פלט:

[3 7 19 42 88]
[Bob Carl alice lisa]
true
2 true

מחרוזות ממוינות לפי בתים, כך שכל אות ASCII גדולה באה לפני כל אות קטנה. זה כמעט אף פעם לא מה שמשתמש מצפה לו בשמות. הסעיף הבא מתקן את זה.

החבילה slices הגיעה ב-Go 1.21. ה-Sort שלה הוא pattern-defeating quicksort: O(n log n), במקום, ולא יציב.

סדר מותאם עם SortFunc

slices.SortFunc מקבלת פונקציית השוואה func(a, b T) int. החזירו מספר שלילי כש-a צריך לבוא קודם, מספר חיובי כש-b צריך לבוא קודם, ואפס כשהם שווים. פונקציית העזר cmp.Compare מחזירה בדיוק את זה לטיפוסים ניתנים לסידור.

strings.ToLower בתוך השוואה מקצה מחרוזת חדשה בכל פעם שבקלט שלה יש אות גדולה, וההשוואה רצה בערך n log n פעמים. ב-slices גדולים, חשבו את המפתחות באותיות קטנות פעם אחת. לשמות בשפות אחרות (סימני הטעמה, כללי locale), השתמשו ב-golang.org/x/text/collate, שנמצאת מחוץ לספרייה הסטנדרטית.

פונקציית השוואה חייבת להיות עקבית: אם היא אומרת ש-a בא לפני b, היא חייבת לומר ש-b בא אחרי a. פונקציה ששוברת את זה (למשל, מחזירה -1 בכל פעם ששני ערכים שונים) מייצרת slice בסדר שגוי בלי שום שגיאה. חיסור של מספרים שלמים (return a - b) נראה מסודר אבל גולש בערכים גדולים; השתמשו ב-cmp.Compare.

מיון structs

פונקציית ההשוואה מקבלת איברים, כך שמיון structs לפי שדה הוא אותה קריאה:

עובדים עם משכורות שוות יכולים לצאת כאן בכל סדר, כי SortFunc לא יציבה. אם צריך סדר מובטח, שברו שוויון עם שדות נוספים או השתמשו במיון יציב.

מיון לפי כמה שדות

השוו קודם את השדה החשוב ביותר, ועברו לבא רק במקרה של שוויון. cmp.Or (Go 1.22) מחזירה את הארגומנט הראשון שלה שאינו אפס, וזה הופך את כל זה לשורה אחת:

פלט:

eng     150 Cy
eng     120 Ana
eng     120 Eve
sales    90 Bob
sales    90 Dee

כל השוואה מוערכת גם כשהראשונה כבר מכריעה, כי הן ארגומנטים רגילים. זה עולה מעט בהשוואות של שדות. כששובר השוויון יקר, כתבו ביד את השרשרת if c := ...; c != 0 { return c }.

מיון יציב

מיון יציב שומר איברים שווים בהשוואה בסדר המקורי שלהם. זה חשוב כשלקלט כבר יש סדר משמעותי, למשל רשומות שממוינות לפי זמן ושעכשיו מקבצים אותן לפי משתמש.

זה מדפיס [{ana 2} {ana 4} {bob 1} {bob 3} {bob 5}]: בתוך כל משתמש, הרצף המקורי נשמר. מיון יציב עושה יותר עבודה, אז השתמשו בו רק כשהסדר של איברים שווים חשוב.

מיון map

ל-maps אין סדר. כדי להציג map ממוין לפי מפתח, מיינו את המפתחות שלו: slices.Sorted(maps.Keys(m)) (Go 1.23). כדי למיין לפי ערך, מיינו את המפתחות עם השוואה שמחפשת את הערכים:

שובר השוויון לפי המילה חשוב: בלעדיו, chan ו-slice (שניהם 7) היו מודפסים בסדר שונה מהרצה להרצה, כי המפתחות יוצאים מה-map בסדר אקראי. ראו maps לעוד פרטים.

החבילה sort: sort.Slice והחברים שלה

לפני Go 1.21, מיון עבר דרך החבילה sort. תראו אותה בהרבה קוד קיים:

sort.Ints(nums)
sort.Strings(names)
sort.Slice(staff, func(i, j int) bool {
	return staff[i].Salary < staff[j].Salary
})
sort.SliceStable(staff, func(i, j int) bool { ... })

הבדלים ששווה להכיר:

sort.Sliceslices.SortFunc
הפונקציה מקבלתאינדקסים i, jאיברים a, b
מחזירהbool (האם i קטן מ-j)int (שלילי, אפס, חיובי)
בטיחות טיפוסיםמקבלת any, משתמשת ב-reflectionגנרית, נבדקת בזמן הידור
מהירותאיטית יותרמהירה יותר

באג נפוץ ב-sort.Slice הוא closure שסוגר על slice אחר מזה שממוינים, כי פונקציית ה-less ניגשת לפי מיקום. ב-SortFunc הבאג הזה לא יכול לקרות כי היא מעבירה לכם את האיברים.

הטיפוס sort.Interface (Len, Less, Swap) הוא הצורה הוותיקה ביותר. הוא עדיין הדרך למיין נתונים שאינם slice יחיד, כמו שני slices מקבילים שצריכים לזוז יחד. מאז Go 1.22, sort.Ints, sort.Strings ו-sort.Float64s פשוט קוראות ל-slices.Sort.

טעויות נפוצות

  • ציפייה ש-Sort תחזיר את ה-slice הממוין. היא ממיינת במקום ולא מחזירה כלום. השתמשו ב-slices.Sorted(slices.Values(s)) אם רוצים slice ממוין חדש, או ב-slices.Clone קודם.
  • הנחה שאיברים שווים שומרים על הסדר שלהם. רק גרסאות ה-Stable מבטיחות את זה.
  • חיסור כדי להשוות. a - b גולש. השתמשו ב-cmp.Compare.
  • מיון מספרים עשרוניים עם NaN. cmp.Compare מסדרת את NaN לפני כל ערך אחר, וזה שומר על מיון עקבי. השוואה a < b שנכתבה ביד לא עושה את זה.

שאלות נפוצות

איך ממיינים slice ב-Go?

למספרים ולמחרוזות, קראו ל-slices.Sort(s) (Go 1.21). היא ממיינת במקום בסדר עולה. לכל דבר אחר, או לסדר אחר, השתמשו ב-slices.SortFunc(s, func(a, b T) int { ... }), שבה הפונקציה מחזירה מספר שלילי אם a בא קודם, חיובי אם b בא קודם, ואפס אם הם שווים.

איך ממיינים slice בסדר יורד ב-Go?

החליפו את סדר הארגומנטים בהשוואה: slices.SortFunc(s, func(a, b int) int { return cmp.Compare(b, a) }). או מיינו בסדר עולה ואז קראו ל-slices.Reverse(s).

איך ממיינים slice של structs לפי כמה שדות ב-Go?

השוו את השדה הראשון, ועברו לבא רק כשהוא שווה. cmp.Or (Go 1.22) עושה בדיוק את זה: return cmp.Or(cmp.Compare(a.Dept, b.Dept), cmp.Compare(b.Salary, a.Salary), strings.Compare(a.Name, b.Name)) מחזירה את התוצאה הראשונה שאינה אפס.

מה ההבדל בין sort.Slice לבין slices.SortFunc?

sort.Slice(s, func(i, j int) bool) הוא ה-API הישן: הוא מקבל פונקציית less על אינדקסים ומשתמש ב-reflection כדי להחליף איברים. slices.SortFunc(s, func(a, b T) int) גנרית, נבדקת בזמן הידור, מקבלת את האיברים ישירות ומהירה יותר. קוד חדש צריך להשתמש בחבילה slices; sort.Slice עדיין נפוצה בקוד שנכתב לפני Go 1.21.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל