Now that you know what a slice is and how to create one, it is time to explore how to put the items contained within the slice in a specific order. In Go, we can do this with the help of the standard library package sort.
In this topic, we'll learn how to use the functions from the sort package to arrange string and numeric types contained in a slice, in both ascending or descending order.
Sorting a slice of integers
One of the most common programming tasks is sorting a list of integers. In Go, we can easily sort a list of int types in ascending order using the sort.Ints() function:
package main
import (
"fmt"
"sort"
)
func main() {
nums := []int{7, 2, 8, -9, 4, 0, -1}
fmt.Printf("%v <- Unsorted slice\n", nums)
sort.Ints(nums)
fmt.Printf("%v <- Sorted in ascending order", nums)
}
// Output:
// [7 2 8 -9 4 0 -1] <- Unsorted slice
// [-9 -1 0 2 4 7 8] <- Sorted in ascending orderThe sort.Ints() function takes a slice of ints: nums as an argument and sorts it in ascending order. The sort.Ints() function doesn't return any value; it only sorts in ascending order the slice we pass as an argument to it.
Take notice that we can also sort negative int values! In the above example, we can see that the sorted output begins with the smallest negative value -9 and ends with 8.
Sorting a slice of floats
Just like the previous example with int types, we can also sort float64 type values in Go using the sort.Float64s() function:
package main
import (
"fmt"
"math"
"sort"
)
func main() {
nums := []float64{3.1416, 2.7182, 1.6180, -273.15, math.NaN(), math.Inf(1), math.Inf(-1)}
fmt.Printf("%v <- Unsorted slice\n", nums)
sort.Float64s(nums)
fmt.Printf("%v <- Sorted in ascending order", nums)
}
// Output:
// [3.1416 2.7182 1.618 -273.15 NaN +Inf -Inf] <- Unsorted slice
// [NaN -Inf -273.15 1.618 2.7182 3.1416 +Inf] <- Sorted in ascending orderThe sort.Float64s() function works just like the sort.Ints() function. It takes as an argument a slice of float64 values, and instead of returning something, it just sorts the slice of float64 types in ascending order.
An important detail regarding the sort.Float64s() function is that it orders values such as NaN before other values, even before -Inf.
Sorting a slice of strings
Another common operation is sorting a slice of strings in ascending or alphabetical order (from a to z). We can do this with the help of the sort.Strings() function:
...
func main() {
lastNames := []string{"Messi", "Salah", "Ronaldo", "Haaland", "Mbappé"}
fmt.Printf("%v <- Unsorted slice\n", lastNames)
sort.Strings(lastNames)
fmt.Printf("%v <- Sorted in alphabetical order", lastNames)
}
// Output:
// [Messi Salah Ronaldo Haaland Mbappé] <- Unsorted slice
// [Haaland Mbappé Messi Ronaldo Salah] <- Sorted in alphabetical orderThe sort.Strings() function sorts in alphabetical order the slice of strings we pass to it and doesn't return anything.
An important detail regarding the sort.Strings() function is that if we try to sort accented words, we might not get an accurate alphabetical order sort, for example:
...
func main() {
cities := []string{"Ürkmez", "İstanbul", "München", "Muğla", "Ulm", "Zürich"}
fmt.Printf("%v <- Unsorted slice\n", cities)
sort.Strings(cities)
fmt.Printf("%v <- Sorted in alphabetical order\n", cities)
}
// Output:
// [Ürkmez İstanbul München Muğla Ulm Zürich] <- Unsorted slice
// [Muğla München Ulm Zürich Ürkmez İstanbul] <- Sorted in alphabetical orderAs you can see, İstanbul and Ürkmez end up at the end of the slice. To sort accented words properly, we would need to use an external package such as thex/text package. If you want to know more about sorting words in different languages using the x/text package, you can take a look at this blog post.
The sort.Slice() function
So far, we've seen the most basic sorting operations: sorting numbers and strings in ascending order. However, what if we need to sort a slice of integers or strings in reverse/descending order, or if we need to sort a slice based on the length of each word?
The sort package has a convenient function sort.Slice(), which allows us to sort a slice based on a comparison function.
Let's take a look at how we can use sort.Slice() with an anonymous comparison function to sort the words slice in descending order:
...
func main() {
words := []string{"Epsilon", "Zeta", "Gamma", "Alpha", "Beta"}
fmt.Printf("%v <- Unsorted 'words' slice\n", words)
// Here we use the '>' operator to return the words in descending order (from z to a)
sort.Slice(words, func(i, j int) bool { return words[i] > words[j] })
fmt.Printf("%v <- Sorted 'words' in descending order", words)
}
// Output:
// [Epsilon Zeta Gamma Alpha Beta] <- Unsorted 'words' slice
// [Zeta Gamma Epsilon Beta Alpha] <- Sorted 'words' in descending orderIn the above example, we pass the words slice as an argument to the sort.Slice() function. After that, we create an anonymous function with two arguments – i and j; then we declare a bool return type for the anonymous function.
Within the body of the anonymous function lies the crucial part of our sort implementation. The return words[i] > words[j] statement makes our anonymous function return the words within the slice in descending order (from z to a).
Take notice that if we wanted to sort the words in ascending order, we would only need to change the operator within the return statement to the "less than", <, operator – return words[i] < words[j]
Sorting structs with multiple fields
Now that we know the basics about the sort.Slice() function, let's take a look at how we can use it to sort a struct that contains two fields of different types.
In this case, we'll sort the students struct slice. We'll sort the students with the highest score first, and in case any student has the same score, we'll sort them by their fullName in alphabetical order:
...
type Student struct {
fullName string
score float64
}
func main() {
students := []Student{
{"Harry Potter", 100},
{"Hermione Granger", 100},
{"Ron Weasley", 80},
{"Draco Malfoy", 95},
}
sort.Slice(students, func(i, j int) bool {
if students[i].score != students[j].score {
// here, we sort the students by the highest score first
return students[i].score > students[j].score
}
// then, if any students have the same score,
//we sort them by their 'fullName' in alphabetical order
return students[i].fullName < students[j].fullName
})
for _, s := range students {
fmt.Println(s.fullName, s.score)
}
}
// Output:
// Harry Potter 100
// Hermione Granger 100
// Draco Malfoy 95
// Ron Weasley 80Let's examine the sorting logic of the anonymous function within sort.Slice(). First, we compare the score between two students and sort them by the highest score. If the scores are identical, we sort the students by their fullName in alphabetical order.
Stable sort for slices
One of the well-known properties of sorting is stability. In the sort package, we can find the sort.SliceStable() function. It works just like the sort.Slice() function; however, it guarantees that the sorted elements preserve the same initial order:
...
type Person struct {
name string
age int
}
func main() {
people := []Person{
{"Amy", 25},
{"Eli", 75},
{"Amy", 75},
{"Bob", 75},
{"Bob", 25},
{"Joe", 25},
{"Eli", 25},
}
// sort by name, preserving original order
sort.SliceStable(people, func(i, j int) bool { return people[i].name < people[j].name })
fmt.Printf("%v <- Stable sort - by name\n", people)
// sort by age, preserving name order
sort.SliceStable(people, func(i, j int) bool { return people[i].age < people[j].age })
fmt.Printf("%v <- Stable sort - by age & name", people)
}
// Output:
// [{Amy 25} {Amy 75} {Bob 75} {Bob 25} {Eli 75} {Eli 25} {Joe 25}] <- Stable sort - by name
// [{Amy 25} {Bob 25} {Eli 25} {Joe 25} {Amy 75} {Bob 75} {Eli 75}] <- Stable sort - by age & nameAs you can see in the program output, the original order of the names declared within the people struct slice is preserved when we execute the first sort.SliceStable() function call. After that, we make a second call to the sort.SliceStable() function, and in this case, we sort the people by age, preserving the previous "sorted by name" order.
Summary
In this topic, we have learned how to sort slices of primitive types using the sort.Ints(), sort.Float64s() and sort.Strings() functions from the sort package. We've also learned how to sort slices in non-stable and stable ways using the sort.Slice() and sort.SliceStable() functions, respectively.
To sum up:
sort.Ints(),sort.Float64s()andsort.Strings()are simple functions that allow us to sort slices of primitive types in ascending order.The
sort.Slice()andsort.SliceStable()functions allow us to implement an anonymous comparison function within them to perform more complex sorting, such as sorting a slice in descending order or sorting based on the length of a string using the built-inlenfunction.The
sort.SliceStable()function provides a stable sorting algorithm. It guarantees that the sorted elements preserve the same initial order.
Enough reading for now! It's time to test our knowledge and solve some theory and coding tasks to make sure we've learned how to properly use the functions within the sort package.