A guide to sorting in Go

Programming languages often provide ready-made helpers for sorting collections due to the incredibly common nature of this task. Go is no different with its provision of the sort package for all your sorting needs.

Sorting data is one of the most common tasks you may find yourself performing ever so often. This article explores a few techniques in Go that should make the sorting of any data collection a breeze.

Sort a slice of strings

The sort.Strings method sorts a slice of strings in increasing order as shown below:

func main() {
	s := []string{"James", "John", "Peter", "Andrew", "Matthew", "Luke"}
	sort.Strings(s)
	fmt.Println(s) // [Andrew James John Luke Matthew Peter]
}

The sort.StringsAreSorted method also exists to help you check if a string slice is in its sorted form (that is, in increasing order):

func main() {
	s := []string{"James", "John", "Peter", "Andrew", "Matthew", "Luke"}
	fmt.Println(sort.StringsAreSorted(s)) // false
	sort.Strings(s)
	fmt.Println(sort.StringsAreSorted(s)) // true
}

Sort a slice of integers

To sort a slice of integers, use the sort.Ints method as shown below:

func main() {
	intSlice := []int{4, 5, 2, 1, 3, 9, 7, 8, 6}
	fmt.Println(sort.IntsAreSorted(intSlice)) // false
	sort.Ints(intSlice)
	fmt.Println(intSlice) // [1 2 3 4 5 6 7 8 9]
	fmt.Println(sort.IntsAreSorted(intSlice)) // true
}

Sort a slice of floats

A slice of float64s may be sorted in increasing order using sort.Float64s:

func main() {
	f := []float64{math.NaN(), -0.2, -1.3, 0.9, 4.8, 2.1}
	fmt.Println(sort.Float64sAreSorted(f)) // false
	sort.Float64s(f)
	fmt.Println(f) // [NaN -1.3 -0.2 0.9 2.1 4.8]
	fmt.Println(sort.Float64sAreSorted(f)) // true
}

Sort a slice of structs

To sort a slice of structs in Go, you need to use a less function along with either the sort.Slice or sort.SliceStable methods. Here’s how we can sort a slice of persons according to their names and ages for example:

type Person struct {
	name string
	age  int
}

func main() {
	{%raw %}people := []Person{{"Sally", 20}, {"David", 40}, {"Jon", 30}, {"Larry", 25}}{% endraw %}
	sort.SliceStable(people, func(i, j int) bool {
		return people[i].age < people[j].age
	})
	fmt.Println("Sorted by age: ", people) // Sorted by age:  [{Sally 20} {Larry 25} {Jon 30} {David 40}]

	sort.SliceStable(people, func(i, j int) bool {
		return people[i].name < people[j].name
	})
	fmt.Println("Sorted by name: ", people) // Sorted by name:  [{David 40} {Jon 30} {Larry 25} {Sally 20}]
}

Returning true from the less function will cause the element at index i to be sorted to a lower position than index j (The element at index i will come first in the sorted slice). Otherwise, the element at index j will come first if false is returned.

The difference between sort.Slice and sort.SliceStable is that the latter will keep the original order of equal elements while the former may not.

Sort a struct slice using multiple keys

In the example below, we’ll sort a slice of Person structs by LastName, and then by FirstName so that if two people have the same LastName, they’ll be ordered according to their first names.

type Person struct {
	FirstName string
	LastName  string
}

func main() {
	people := []Person{
		{"Michael", "Jackson"},
		{"Janet", "Jackson"},
		{"Keanu", "Reeves"},
		{"Reverend", "King"},
		{"Jane", "Austen"},
	}

	sort.SliceStable(people, func(i, j int) bool {
		if people[i].LastName != people[j].LastName {
			return people[i].LastName < people[j].LastName
		}

		return people[i].FirstName < people[j].FirstName
	})

	// Notice that Janet Jackson comes before Michael Jackson
	fmt.Println(people) // [{Jane Austen} {Janet Jackson} {Michael Jackson} {Reverend King} {Keanu Reeves}]
}

Sort a map by key or value

Maps are unordered collections in Go so there is no provision for sorting a map using the sort package. However, if you really need to sort a map, you can try the methods described below.

From Go 1.12, you can just print the map and it’ll be sorted by keys:

func main() {
	m := map[string]int{
		"sally": 45,
		"john":  5,
		"marcy": 15,
		"duff":  10,
		"tom":   20,
	}

	fmt.Println(m) // map[duff:10 john:5 marcy:15 sally:45 tom:20]
}

If you intend to do more than just print the sorted map or if you want to sort by value instead, you can create an empty slice of key-value pairs whose capacity is equal to the length of the map, iterate over the map and add an entry for each key-value pair to the slice, then sort the slice using sort.SliceStable. Once the slice is sorted, you can iterate over it and access the map value associated with the key.

func main() {
	m := map[string]int{
		"sally": 45,
		"john":  5,
		"marcy": 15,
		"duff":  10,
		"tom":   20,
	}

	// Create a struct for each map key-value pair
	type KeyValue struct {
		Key   string
		Value int
	}

	// create an empty slice of key-value pairs
	s := make([]KeyValue, 0, len(m))
	// append all map keys-value pairs to the slice
	for k, v := range m {
		s = append(s, KeyValue{k, v})
	}

	// sort the slice of key-value pairs by key...
	sort.SliceStable(s, func(i, j int) bool {
		return s[i].Key < s[j].Key
	})

	for _, v := range s {
		fmt.Println(v.Key, m[v.Key])
	}
	// Output:
	// duff 10
	// john 5
	// marcy 15
	// sally 45
	// tom 20

	// sort the slice of key-value pairs by value...
	sort.SliceStable(s, func(i, j int) bool {
		return s[i].Value < s[j].Value
	})

	for _, v := range s {
		fmt.Println(v.Key, m[v.Key])
	}
	// Output:
	// john 5
	// duff 10
	// marcy 15
	// tom 20
	// sally 45
}

Conclusion

Sorting collections of data is pretty straightforward in Go through the use of the sort package and the solutions presented above should suffice for the majority of situations you will face. If you know of a different way to sort that is not covered in this article, please do share it in the comments section below.

Thanks for reading, and happy coding!