Golang Sorting and Custom sorting by function

Sorting is a very common use case that we encounter in day-to-day programming. In this article, you’ll learn how to sort a slice of primitive types (string, int, float64) and user-defined collections in Go.

Sorting a Slice of Strings, Integers, or Floats in Go

The sort package in Go provides several convenience methods for sorting a slice of primitive types. The following example demonstrates how to sort a slice of strings, integers, and floats in Go:

package main

import (
	"fmt"
	"sort"
)

func main() {
	// Sorting a slice of Strings
	strs := []string{"quick", "brown", "fox", "jumps"}
	sort.Strings(strs)
	fmt.Println("Sorted strings: ", strs)

	// Sorting a slice of Integers
	ints := []int{56, 19, 78, 67, 14, 25}
	sort.Ints(ints)
	fmt.Println("Sorted integers: ", ints)

	// Sorting a slice of Floats
	floats := []float64{176.8, 19.5, 20.8, 57.4}
	sort.Float64s(floats)
	fmt.Println("Sorted floats: ", floats)
}
$ go run sorting.go
Sorted strings:  [brown fox jumps quick]
Sorted integers:  [14 19 25 56 67 78]
Sorted floats:  [19.5 20.8 57.4 176.8]

Sorting a Slice in Reverse order

To sort a slice in reverse order, you can use the Reverse() function along with StringSlice, IntSlice, and Float64Slice types. These types implement the Interface for comparision and swap defined in the sort package.

The Reverse() function returns the reverse order of data. Let’s look at an example of sorting a slice of strings, integers, and floats in reverse order:

package main

import (
	"fmt"
	"sort"
)

func main() {
	// Sorting a slice of Strings
	strs := []string{"quick", "brown", "fox", "jumps"}
	sort.Sort(sort.Reverse(sort.StringSlice(strs)))
	fmt.Println("Sorted strings in reverse order: ", strs)

	// Sorting a slice of Integers
	ints := []int{56, 19, 78, 67, 14, 25}
	sort.Sort(sort.Reverse(sort.IntSlice(ints)))
	fmt.Println("Sorted integers in reverse order: ", ints)

	// Sorting a slice of Floats
	floats := []float64{176.8, 19.5, 20.8, 57.4}
	sort.Sort(sort.Reverse(sort.Float64Slice(floats)))
	fmt.Println("Sorted floats in reverse order: ", floats)
}
$ go run reverse-sorting.go
Sorted strings in reverse order:  [quick jumps fox brown]
Sorted integers in reverse order:  [78 67 56 25 19 14]
Sorted floats in reverse order:  [176.8 57.4 20.8 19.5]

Sorting a slice using a comparator function in Go

Many times, you would want to sort a collection by something other than its natural order. For example, you would want to sort a slice of strings by their length, or you would want to sort a slice of users by their age.

For these usecases, you can use the Slice() and SliceStable() functions provided by the sort package. These are higher order functions that accept a less function as an argument:

func Slice(x interface{}, less func(i, j int) bool)
func SliceStable(x interface{}, less func(i, j int) bool)

The less function is a comparator function that reports whether the element at index i should sort before the element at index j. If both less(i, j) and less(j, i) are false, then the elements at index i and j are considered equal.

When you use the Slice() function, the sort is not guaranteed to be stable: equal elements may be reversed from their original order. For a stable sort, use SliceStable().

Let’s look at an example:

package main

import (
	"fmt"
	"sort"
)

func main() {
	// Sorting a slice of strings by length
	strs := []string{"United States", "India", "France", "United Kingdom", "Spain"}
	sort.Slice(strs, func(i, j int) bool {
		return len(strs[i]) < len(strs[j])
	})
	fmt.Println("Sorted strings by length: ", strs)

	// Stable sort
	sort.SliceStable(strs, func(i, j int) bool {
		return len(strs[i]) < len(strs[j])
	})
	fmt.Println("[Stable] Sorted strings by length: ", strs)

	// Sorting a slice of strings in the reverse order of length
	sort.SliceStable(strs, func(i, j int) bool {
		return len(strs[j]) < len(strs[i])
	})
	fmt.Println("[Stable] Sorted strings by reverse order of length: ", strs)
}
$ go run sorting-by-comparator.go
Sorted strings by length:  [India Spain France United States United Kingdom]
[Stable] Sorted strings by length:  [India Spain France United States United Kingdom]
[Stable] Sorted strings by reverse order of length:  [United Kingdom United States France India Spain]

Sorting a slice of structs using a comparator function

This is a continuation of the previous example. This example demonstrates how to sort a collection of user-defined structs by passing a custom less function to the Slice() and SliceStable() functions:

package main

import (
	"fmt"
	"sort"
)

type User struct {
	Name string
	Age  int
}

func main() {
	// Sorting a slice of structs by a field
	users := []User{
		{
			Name: "Rajeev",
			Age:  28,
		},
		{
			Name: "Monica",
			Age:  31,
		},
		{
			Name: "John",
			Age:  56,
		},
		{
			Name: "Amanda",
			Age:  16,
		},
		{
			Name: "Steven",
			Age:  28,
		},
	}

    // Sort users by their age
	sort.Slice(users, func(i, j int) bool {
		return users[i].Age < users[j].Age
	})
	fmt.Println("Sorted users by age: ", users)

	// Stable sort
	sort.SliceStable(users, func(i, j int) bool {
		return users[i].Age < users[j].Age
	})
	fmt.Println("Sorted users by age: ", users)
}

Custom sorting by implementing sort.Interface

Finally, let’s look at the most generic way of sorting a collection of primitives or user-defined structs in Go. To enable custom sorting of a collection of any type, you need to define a corresponding type that implements the generic Interface provided by the sort package. The Interface contains the following methods:

type Interface interface {
	// Len is the number of elements in the collection.
	Len() int

	// Less reports whether the element with index i must sort before the element with index j.
	// If both Less(i, j) and Less(j, i) are false, then the elements at index i and j are considered equal.
	Less(i, j int) bool

	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}

After implementing the above Interface, you can use the Sort() or Stable() functions that sort any collection that implements the sort.Interface interface.

Let’s understand with an example:

package main

import (
	"fmt"
	"sort"
)

type User struct {
	Name string
	Age  int
}

// Define a collection type that implements sort.Interface
type UsersByAge []User

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

func main() {
	users := []User{
		{
			Name: "Rajeev",
			Age:  28,
		},
		{
			Name: "Monica",
			Age:  31,
		},
		{
			Name: "John",
			Age:  56,
		},
		{
			Name: "Amanda",
			Age:  16,
		},
		{
			Name: "Steven",
			Age:  28,
		},
	}

	// Sorting a slice of users by age (Sort may place equal elements in any order in the final result)
	sort.Sort(UsersByAge(users))
	fmt.Println("Sorted users by age: ", users)

	// Stable Sorting (Stavle sort preserves the original input order of equal elements)
	sort.Stable(UsersByAge(users))
	fmt.Println("[Stable] Sorted users by age: ", users)

}
$ go run custom-sorting-struct.go
Sorted users by age:  [{Amanda 16} {Rajeev 28} {Steven 28} {Monica 31} {John 56}]
[Stable] Sorted users by age:  [{Amanda 16} {Rajeev 28} {Steven 28} {Monica 31} {John 56}]