ZhangZhihui's Blog  

Problem: You want to sort elements in an array or slice.


Solution: For int , float64 , and string arrays or slices you can use sort.Ints , sort.Float64s , and sort.Strings . You can also use a custom comparator by using sort.Slice . For structs, you can create a sortable interface by implementing the sort.Interface interface and then using sort.Sort to sort the array or slice.

package main

import (
    "fmt"
    "sort"
)

func main() {
    integers := []int{3, 14, 159, 26, 53}
    floats := []float64{3.14, 1.41, 1.73, 2.72, 4.53}
    strings := []string{"the", "quick", "brown", "fox", "jumped"}

    sort.Ints(integers)
    fmt.Println(integers)
    sort.Sort(sort.Reverse(sort.IntSlice(integers)))  // sort.IntSlice is a type that implements sort.Interface interface
    fmt.Println(integers)
    sort.Float64s(floats)
    fmt.Println(floats)
    sort.Sort(sort.Reverse(sort.Float64Slice(floats)))  // sort.Float64SLice is a type that implements sort.Interface interface
    fmt.Println(floats)
    sort.Strings(strings)
    fmt.Println(strings)
    sort.Sort(sort.Reverse(sort.StringSlice(strings)))  // sort.StringSlice is a type that implements sort.Interface interface
    fmt.Println(strings)
}

 

zzh@ZZHPC:/zdata/Github/mastering-go-expertise$ go run main.go
[3 14 26 53 159]
[159 53 26 14 3]
[1.41 1.73 2.72 3.14 4.53]
[4.53 3.14 2.72 1.73 1.41]
[brown fox jumped quick the]
[the quick jumped fox brown]

 

This is sorted in ascending order. What if you want to sort it in descending order? There is no ready-made function to sort in descending order, but you can easily use a simple for loop to reverse the sorted slice:

    for i := len(integers)/2 - 1; i >= 0; i-- {
        opp := len(integers) - 1 - i
        integers[i], integers[opp] = integers[opp], integers[i]
    }

    fmt.Println(integers)

Simply find the middle of the slice, and then using a loop, exchange the elements with their opposite side, starting from that middle. If you run the preceding snippet, this is what you will get:

[159  53  26  14  3]

You can also use the sort.Slice function, passing in your less function:

    sort.Slice(floats, func(i, j int) bool {
        return floats[i] > floats[j]
    })
    fmt.Println(floats)

This will produce the following output:

[4.53  3.14  2.72  1.73  1.41]

The less function, the second parameter in the sort.Slice function, takes in two parameters i and j , indices of the consecutive elements of the slice. It’s supposed to return true if the element at i is less than the element at j when sorting.

What if the elements are the same? Using sort.Slice means the original order of the elements might be reversed (or remain the same). If you want the order to be consistently the same as the original, you can use sort.SliceStable .

 

The sort.Slice function works with slices of any type, so this means you can also sort custom structs:

type Person struct {
    Name string
    Age  uint8
}

func main() {
    people := []Person{
        {"Alice", 22},
        {"Bob", 18},
        {"Charlie", 23},
        {"Dave", 27},
        {"Eve", 31},
    }
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age < people[j].Age
    })
    fmt.Println(people)
}

If you run the code you will get the following output, with the people slice sorted according to the ages of the people:

zzh@ZZHPC:/zdata/MyPrograms/Go/study$ go run main.go
[{Bob 18} {Alice 22} {Charlie 23} {Dave 27} {Eve 31}]

Another way of sorting structs is by implementing the sort.Interface .

Here’s how you can do this for the Person struct:

type Person struct {
    Name string
    Age  uint8
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func main() {
    people := []Person{
        {"Alice", 22},
        {"Bob", 18},
        {"Charlie", 23},
        {"Dave", 27},
        {"Eve", 31},
    }
    sort.Sort(ByAge(people))
    fmt.Println(people)
}

You want to sort a slice of structs, so you need to associate the interface functions to the slice, not the struct. Create a type named ByAge that is a slice of Person structs. Next, you associate the Len , Less , and Swap functions to ByAge , making it a struct that implements sort.Interface . The Less method here is the same as the one used in the sort.Slice function earlier.

Using this is quite simple. You cast people to ByAge , and pass that into sort.Sort

If you run this code, you will see the following results:

zzh@ZZHPC:/zdata/MyPrograms/Go/study$ go run main.go
[{Bob 18} {Alice 22} {Charlie 23} {Dave 27} {Eve 31}]

Implementing sort.Interface is a bit long-winded, but there are certainly some advantages. For one, you can use sort.Reverse to sort by descending order:

    sort.Sort(sort.Reverse(ByAge(people)))
    fmt.Println(people)

This produces the following output:

zzh@ZZHPC:/zdata/MyPrograms/Go/study$ go run main.go
[{Eve 31} {Dave 27} {Charlie 23} {Alice 22} {Bob 18}]

You can also use the sort.IsSorted function to check if the slice is already sorted:

type Person struct {
    Name string
    Age  uint8
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func main() {
    people := []Person{
        {"Alice", 22},
        {"Bob", 18},
        {"Charlie", 23},
        {"Dave", 27},
        {"Eve", 31},
    }

    b := ByAge(people)
    fmt.Println("b:", b, sort.IsSorted(b)) // false
    fmt.Println("-------------------------------------------------------------------")
    r := sort.Reverse(b)
    fmt.Println("b:", b, sort.IsSorted(b)) // false
    fmt.Println("r:", r, sort.IsSorted(r)) // false
    fmt.Println("-------------------------------------------------------------------")
    sort.Sort(r)
    fmt.Println("b:", b, sort.IsSorted(b)) // false
    fmt.Println("r:", r, sort.IsSorted(r)) // true
    fmt.Println("-------------------------------------------------------------------")
    sort.Sort(b)
    fmt.Println("b:", b, sort.IsSorted(b)) // true
}

 

zzh@ZZHPC:/zdata/Github/ztest$ go run main.go
b: [{Alice 22} {Bob 18} {Charlie 23} {Dave 27} {Eve 31}] false
-------------------------------------------------------------------
b: [{Alice 22} {Bob 18} {Charlie 23} {Dave 27} {Eve 31}] false
r: &{[{Alice 22} {Bob 18} {Charlie 23} {Dave 27} {Eve 31}]} false
-------------------------------------------------------------------
b: [{Eve 31} {Dave 27} {Charlie 23} {Alice 22} {Bob 18}] false
r: &{[{Eve 31} {Dave 27} {Charlie 23} {Alice 22} {Bob 18}]} true
-------------------------------------------------------------------
b: [{Bob 18} {Alice 22} {Charlie 23} {Dave 27} {Eve 31}] true

 

The biggest advantage, though, is that using sort.Interface is a lot more performant than using sort.Slice.

As you can see, using sort.Interface is more efficient. This is because sort.Slice uses any as the first parameter. This means it takes in any structs but is less efficient.

posted on 2023-10-08 10:50  ZhangZhihuiAAA  阅读(7)  评论(0编辑  收藏  举报