Sorting
Sorting numbers or strings in their natural order is one call: slices.Sort. Real data needs more — products by price, people by surname then first name, newest first. This lesson shows how to sort with a comparison function using slices.SortFunc and the cmp package, how to sort by several keys, stable sorting, and a quick look at the older sort package you'll still meet in existing code.
Natural order
package main import ( "fmt" "slices" ) func main() { nums := []int{42, 7, 19, 3} slices.Sort(nums) fmt.Println(nums) words := []string{"pear", "Apple", "fig", "banana"} slices.Sort(words) fmt.Println(words) }
[3 7 19 42] [Apple banana fig pear]
slices.Sort sorts in place, smallest first, and works for any ordered type: integers, floats and strings. Strings sort by bytes, so uppercase letters come before lowercase.
Custom order with SortFunc
slices.SortFunc takes a comparison function cmp(a, b) that returns a negative number if a should come before b, a positive number if after, and zero if they're equal. cmp.Compare produces exactly that for any two ordered values:
package main import ( "cmp" "fmt" "slices" "strings" ) type Product struct { Name string Price float64 } func main() { products := []Product{ {"Lamp", 34.5}, {"Desk", 249}, {"Chair", 129}, {"Mug", 8.9}, } slices.SortFunc(products, func(a, b Product) int { return cmp.Compare(a.Price, b.Price) }) fmt.Println(products) slices.SortFunc(products, func(a, b Product) int { return cmp.Compare(b.Price, a.Price) // swapped: most expensive first }) fmt.Println(products[0].Name) words := []string{"pear", "Apple", "fig", "banana"} slices.SortFunc(words, func(a, b string) int { return strings.Compare(strings.ToLower(a), strings.ToLower(b)) }) fmt.Println(words) }
[{Mug 8.9} {Lamp 34.5} {Chair 129} {Desk 249}]
Desk
[Apple banana fig pear]To reverse an order, swap a and b in the comparison. For case-insensitive sorting, compare lowercased strings.
Sorting by several keys
cmp.Or returns the first of its arguments that isn't zero, which makes multi-key comparisons read like a list of tie-breakers:
package main import ( "cmp" "fmt" "slices" ) type Person struct { Last, First string Age int } func main() { people := []Person{ {"Silva", "Rui", 41}, {"Costa", "Ana", 29}, {"Silva", "Ana", 35}, {"Costa", "Ana", 22}, } slices.SortFunc(people, func(a, b Person) int { return cmp.Or( cmp.Compare(a.Last, b.Last), // by surname, cmp.Compare(a.First, b.First), // then first name, cmp.Compare(a.Age, b.Age), // then age ) }) for _, p := range people { fmt.Println(p.Last, p.First, p.Age) } }
Costa Ana 22 Costa Ana 29 Silva Ana 35 Silva Rui 41
Stable sorting
SortFunc doesn't promise to keep equal elements in their original order. When that order matters — for instance, you sorted by date earlier and now want to group by category without losing the date order — use slices.SortStableFunc:
package main import ( "cmp" "fmt" "slices" ) type Task struct { Title string Priority int } func main() { tasks := []Task{ // already in the order they were added {"write report", 2}, {"fix bug", 1}, {"email team", 2}, {"deploy", 1}, {"lunch", 3}, } slices.SortStableFunc(tasks, func(a, b Task) int { return cmp.Compare(a.Priority, b.Priority) }) for _, t := range tasks { fmt.Println(t.Priority, t.Title) } }
1 fix bug 1 deploy 2 write report 2 email team 3 lunch
Within each priority, tasks keep the order they were added in.
Sorting map entries
Maps have no order, so to show a map sorted by value, collect the keys and sort them by their values:
package main import ( "cmp" "fmt" "maps" "slices" ) func main() { sales := map[string]int{"north": 120, "south": 340, "east": 95, "west": 210} regions := slices.SortedFunc(maps.Keys(sales), func(a, b string) int { return cmp.Compare(sales[b], sales[a]) // highest sales first }) for i, r := range regions { fmt.Printf("%d. %-5s %d\n", i+1, r, sales[r]) } }
1. south 340 2. west 210 3. north 120 4. east 95
Searching sorted data
Once data is sorted, slices.BinarySearch finds an element in a handful of steps even among millions. It also returns the position where a missing value would go, which is how you insert while keeping the slice sorted:
package main import ( "fmt" "slices" ) func main() { ids := []int{3, 8, 15, 23, 42} i, found := slices.BinarySearch(ids, 23) fmt.Println(i, found) i, found = slices.BinarySearch(ids, 10) fmt.Println(i, found) ids = slices.Insert(ids, i, 10) fmt.Println(ids) }
3 true 2 false [3 8 10 15 23 42]
The older sort package
Code written before Go 1.21 uses the sort package, whose sort.Slice takes a "less" function working on indexes. It still works, but the slices functions are faster, type-safe and easier to read, so prefer them in new code:
package main import ( "fmt" "sort" ) func main() { ages := []int{41, 29, 35} sort.Slice(ages, func(i, j int) bool { return ages[i] < ages[j] }) fmt.Println(ages) names := []string{"cleo", "ana", "ben"} sort.Strings(names) // older equivalent of slices.Sort fmt.Println(names) }
[29 35 41] [ana ben cleo]
Try it yourself
Sort the films by rating (highest first), and alphabetically by title when ratings tie.
8.5 Alien 8.3 Heat 8.3 Up 7.9 Brazil 7.2 Cars