Start learning free

LESSON 266 OF 671 · SORTING & BUILT-INS

Custom Sorting in Go: sort.Slice with Your Own Rule

Lesson Read the full explanation

TL ; DR:

To sort by your own rule, give sort.Slice a function. It gets two indexes, i and j. It returns true when item i goes first.


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]
    })
// [41 13 33 5 25]

scores := []int{70, 95, 82}

sort.Slice(scores, func(i, j int) bool { return scores[i] > scores[j] })
// [95 82 70]


Full lesson:

You met sort.Strings in the Shopping Cart project. It sorts a slice from A to Z. Sometimes you need another order. The shortest word may have to come first, or the highest score.

The sort.Slice function sorts by your own rule. Its second value 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.

The function gets two indexes, i and j. It returns true when the item at i must go before the item at j. The sort.Slice function calls it again and again, on two items at a time.

Watch out. The function gets indexes, not the items. Read the items with words[i] and words[j].

  • High to low: return scores[i] > scores[j].
  • Shortest first: return len(words[i]) < len(words[j]).

For two rules, check the first rule. Use the second one only on a tie, as the if above does.

Above, the first rule compares the last digits, nums[i] % 10 and nums[j] % 10. Numbers with the same last digit then go from small to large.

Comparing two strings with < puts them from A to Z.

The sort.Slice function doesn't keep equal items in their old order. Add a second rule when ties matter.

It also changes the slice itself. To keep the original, sort a copy made with slices.Clone(words).


Your task

A pet shop sells toys. Their prices are in prices. Sort prices from high to low.

The shop's pets are in names. Sort names by their last letter, in alphabetical order. Names with the same last letter go from A to Z.

The code already prints both, one item per line.

Expected output:

30
12
8
5
Bella
Luna
Bo
Coco
Max
Rex
STEP 1 OF 4
    Ctrl + Enter to check

    GO · SORTING & BUILT-INS

    Custom Sorting in Go, explained

    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.Slice doesn'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. With words := []string{"pear", "fig", "apple"} sorted by length on a clone, words still prints [pear fig apple] while the copy prints [fig pear apple].
    • In a file of your own, import sort (and slices for Clone).

    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.

    1. Sort prices with sort.Slice and return prices[i] > prices[j].
    2. For the names, call sort.Slice with a body in braces. Inside, put the last letter of names[i] in lastI and the last letter of names[j] in lastJ. The last index is one less than the length, so the letter is names[i][len(names[i])-1]. Indexing a string gives a byte, and bytes compare with < by alphabetical order.
    3. If lastI and lastJ differ, return lastI < lastJ.
    4. 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 i and j as the items. They are positions; i > j compares 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) sorts s in place, using a function that says when item i goes before item j.
    • 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.
    Keep going with Go500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free