Why custom sorting
sort.Strings and sort.Ints sort in the default order, such as A to Z or smallest to largest. Often you need another order: the highest score first, the shortest word first, or the numbers by their last digit. For that, sort.Slice takes your own rule.
Its second argument is a function, written right where you need it. This function has no name: it starts with func, then come its parameters, its result type and its body. It receives two indexes, i and j, and returns true when the item at i must go before the item at j. sort.Slice calls it again and again, on two items at a time, until the slice is in order.
The function gets indexes, not the items. Read the items with words[i] and words[j].
Single rules
scores := []int{70, 95, 82}
sort.Slice(scores, func(i, j int) bool { return scores[i] > scores[j] })
fmt.Println(scores)
[95 82 70]
- High to low:
return scores[i] > scores[j]. - Shortest first:
return len(words[i]) < len(words[j]).
Two rules
For two rules, check the first one and use the second only on a tie:
nums := []int{25, 13, 5, 41, 33}
sort.Slice(nums,
func(i, j int) bool {
if nums[i]%10 != nums[j]%10 {
return nums[i]%10 < nums[j]%10
}
return nums[i] < nums[j]
})
fmt.Println(nums)
[41 13 33 5 25]
The first rule compares the last digits, nums[i] % 10 and nums[j] % 10. Numbers with the same last digit (13 and 33 end in 3, 5 and 25 end in 5) then go from small to large. Comparing two strings with < puts them from A to Z.
Things to know
sort.Slicedoesn't keep equal items in their old order. Add a second rule when ties matter.- It changes the slice itself. To keep the original, sort a copy made with
slices.Clone. Withwords := []string{"pear", "fig", "apple"}sorted by length on a clone,wordsstill prints[pear fig apple]while the copy prints[fig pear apple]. - In a file of your own, import
sort(andslicesforClone).
The lesson's task
A pet shop sells toys with prices []int{12, 5, 30, 8}, and its pets are []string{"Rex", "Luna", "Bella", "Max", "Bo", "Coco"}. Sort the prices from high to low, and sort the names by their last letter, with names that share a last letter going from A to Z.
- Sort
priceswithsort.Sliceandreturn prices[i] > prices[j]. - For the names, call
sort.Slicewith a body in braces. Inside, put the last letter ofnames[i]inlastIand the last letter ofnames[j]inlastJ. The last index is one less than the length, so the letter isnames[i][len(names[i])-1]. Indexing a string gives a byte, and bytes compare with<by alphabetical order. - If
lastIandlastJdiffer, returnlastI < lastJ. - Otherwise, for the same last letter, return
names[i] < names[j]. Max and Rex both end in x, and this puts Max first.
func mainFunction() {
prices := []int{12, 5, 30, 8}
names := []string{"Rex", "Luna", "Bella", "Max", "Bo", "Coco"}
sort.Slice(prices, func(i, j int) bool { return prices[i] > prices[j] })
for _, price := range prices {
fmt.Println(price)
}
sort.Slice(names, func(i, j int) bool {
lastI := names[i][len(names[i])-1]
lastJ := names[j][len(names[j])-1]
if lastI != lastJ {
return lastI < lastJ
}
return names[i] < names[j]
})
for _, name := range names {
fmt.Println(name)
}
}
The prices come out as 30, 12, 8, 5. The names end in a (Bella, Luna), o (Bo, Coco) and x (Max, Rex), so they print as:
Bella
Luna
Bo
Coco
Max
Rex
Common mistakes
- Treating
iandjas the items. They are positions;i > jcompares positions and sorts nothing. - Mixing up the direction.
>puts big values first,<puts small values first. - Skipping the tie rule. Without it, names with the same last letter can come out in any order.
Key takeaways
sort.Slice(s, less)sortssin place, using a function that says when itemigoes before itemj.- The function receives indexes, so it reads the items through the slice.
- For several rules, return early on the first rule that decides, and fall through to the next rule on a tie.
