Guide To Sorting In Go
Nov 16, 2022 · 5 min readIn this post, we’re going to take a look at various ways to sort a slice using the sort
package from Go’s standard library.
Ascending Sort
To sort a slice in ascending order, we can use the Ints(x)
, Float64(x)
, and Strings(x)
from the sort
package.
Examples:
- Sorting
int
nums := []int{3, 7, 8, 1, 5, 4}sort.Ints(nums)fmt.Println(nums) // prints [1 3 4 5 7 8] - Sorting
float64
floats := []float64{3.2, 7.1, 7.9, 1.1, 5.2, 3.9}sort.Float64s(floats)fmt.Println(floats) // prints [1.1 3.2 3.9 5.2 7.1 7.9] - Sorting
string
strings := []string{"apple", "Banana", "cherry", "Mango", "papaya"}sort.Strings(strings)fmt.Println(strings) // [Banana Mango apple cherry papaya]
Notice that sort.Strings(x)
sort the elements in the string slice based on their Unicode value, that’s why all the capital letters come first.
That’s how you can sort in ascending order using Go. Pretty easy and convenient right?
But it’s not as straightforward when we need anything other than the ascending sort, like sorting the slices in descending order for example.
Let’s look at 2 other ways of sorting to solve this problem.
Sort With Custom Comparator Using sort.Slice
sort.Slice()
sorts a slice with the provided less function. But what is a less function?
Less reports whether the element with index i must sort before the element with index j.
The function signature look like this:
less(i, j int) bool
Now, back to our example. We can sort the slice in decreasing order this way:
nums := []int{3, 7, 8, 1, 5, 4}sort.Slice(nums, func(i, j int) bool { return nums[i] > nums[j]})fmt.Println(nums) // prints [8 7 5 4 3 1]
At this point, you should be able to guess how the less function in sort.Ints
is implemented. Check it out!
If you actually visit the link, you’ll notice a pattern in the built-in sorting functions.
// IntSlice attaches the methods of Interface to []int, sorting in increasing order.type IntSlice []intfunc (x IntSlice) Len() int { return len(x) }func (x IntSlice) Less(i, j int) bool { return x[i] < x[j] }func (x IntSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
// Float64Slice implements Interface for a []float64, sorting in increasing order,// with not-a-number (NaN) values ordered before other values.type Float64Slice []float64func (x Float64Slice) Len() int { return len(x) }func (x Float64Slice) Less(i, j int) bool { return x[i] < x[j] || (isNaN(x[i]) && !isNaN(x[j])) }func (x Float64Slice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
// StringSlice attaches the methods of Interface to []string, sorting in increasing order.type StringSlice []stringfunc (x StringSlice) Len() int { return len(x) }func (x StringSlice) Less(i, j int) bool { return x[i] < x[j] }func (x StringSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
That’s because they’re implementing the sort.Interface
interface. Yes, the interface is called Interface
.
And that brings us to the final section, sorting using sort.Sort()
.
Sorting sort.Interface Using sort.Sort()
This option allow the most customization and is actually the basis of how all the sorting function is the sort
package work.
For example, sort.Ints
is actually:
func Ints(x []int) { Sort(IntSlice(x)) }
You can also check the source code.
Now, let’s try to understand what the sort.Sort()
function does.
From the docs:
func Sort(data Interface)
sort.Sort()
sorts data in ascending order as determined by the Less
method. It makes one call to data.Len
to determine n and O(n*log(n)) calls to data.Less
and data.Swap
. The sort is not guaranteed to be stable.
data.Len
, data.Less
, data.Swap
come from the sort.Interface
interface, which look like this:
type Interface interface { // Len is the number of elements in the collection. Len() int // Less reports whether the element with // index i should sort before the element with index j. Less(i, j int) bool // Swap swaps the elements with indexes i and j. Swap(i, j int)}
By implementing sort.Interface
, we can also call sort.Sort()
on our collection.
For example, let’s create a custom struct that implement the interface:
type Pet struct { name string kind string}
type PetSlice []Pet
func (p PetSlice) Len() int { return len(p)}
func (p PetSlice) Less(i, j int) bool { return p[i].name < p[j].name // sort in ascending order using the pet's name}
func (p PetSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i]}
Then we can use our custom struct like this:
var myPets PetSlice = []Pet{ {"luna", "cat"}, {"hery", "dog"}, {"dory", "fish"},}
fmt.Println(myPets) // [{luna cat} {hery dog} {dory fish}]sort.Sort(myPets)fmt.Println(myPets) // [{dory fish} {hery dog} {luna cat}]
Everytime you call sort.Sort
on myPets
, myPets
will be sorted using the Less
method as the comparator.
Finally, let’s go back to our example about sorting int slice in decrementing order. We can also implement a custom type for that.
type myIntSlice []int
func (s myIntSlice) Len() int { return len(s)}
func (s myIntSlice) Less(i, j int) bool { return s[i] > s[j]}
func (s myIntSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i]}
And use it like this:
var nums myIntSlice = []int{3, 7, 8, 1, 5, 4}sort.Sort(nums)fmt.Println(nums) // [8 7 5 4 3 1]
Of course, we don’t always have to declare our variable with our custom type. We can also use type casting for the situation when the data is already there.
Anytime we need to sort an int slice in decreasing order, we can simply type cast it to our custom type.
nums := []int{3, 7, 8, 1, 5, 4}sort.Sort(myIntSlice(nums))fmt.Println(nums) // [8 7 5 4 3 1]
Wrap Up
That’s it for this post. I hope it serves you well as a quick guide to the sort
package.