Sorting collections is a standard task in every programming language. In this article, we’re going to cover the three ways to sort slices in Go.
- Sort int, float64 and string slices
- Sort with comparator function
- Sort with custom data structure
- Binary Search
Prerequisites
Sort int, float64 and string slices
The Go core has functions to sort int, float64 and string slices:
int_slice := []int{3, 2, 4, 2}
sort.Ints(int_slice)
fmt.Println(int_slice)
/// [2 2 3 4]
Sort with comparator function
The function sort.Slice sorts a slice with a function less(i, j int) bool. With sort.StableSlice you can sort a slice with stable order of equal elements.
package main
import (
"fmt"
"sort"
)
func main() {
products := []struct {
Name string
Price float64
}{
{"Microphone", 10.0},
{"Mouse", 20.0},
{"Keyboard", 20.0},
{"Headphone", 15.0},
}
// Sort by price ascending
sort.Slice(products, func(i, j int) bool {
return products[i].Price < products[j].Price
})
fmt.Println("Products sorted by price ascending:")
fmt.Println(products)
// [{Microphone 10} {Headphone 15} {Mouse 20} {Keyboard 20}]
// Sort by price descending
sort.Slice(products, func(i, j int) bool {
return products[i].Price > products[j].Price
})
fmt.Println("Products sorted by price descending:")
fmt.Println(products)
// [{Mouse 20} {Keyboard 20} {Headphone 15} {Microphone 10}]
}
Sort with custom data structure
With the function sort.Sort you can sort custom collection types, that implement sort.Interface.
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)
}
Let’s see how this works with the products type:
type Product struct {
Name string
Price float64
}
// Define a custom slice type
type ProductsByPrice []Product
// Implement sort.Interface
func (p ProductsByPrice) Len() int {
return len(p)
}
// The Less function is just like the comparator function above
func (p ProductsByPrice) Less(i, j int) bool {
return p[i].Price < p[j].Price
}
// Function to swap two elements in a slice
func (p ProductsByPrice) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func main() {
productsByPrice := ProductsByPrice{
{"Microphone", 10.0},
{"Mouse", 20.0},
{"Keyboard", 20.0},
{"Headphone", 15.0},
}
sort.Sort(productsByPrice)
fmt.Println(productsByPrice)
// [{Microphone 10} {Headphone 15} {Mouse 20} {Keyboard 20}]
}
Binary Search
A linear search with complexity O(n) is the best way to find an element in an unsorted slice. One advantage of a sorted slice is that you can perform binary search algorithms, which have a complexity of O(logn). This is great for multiple search operations on a slice with many elements.
Search for int, float64 and string slices
Use the functions:
float_slice := []float64{3.0, 2.0, 4.0, 2.0}
sort.Float64s(float_slice)
index := sort.SearchFloat64s(float_slice, 4)
fmt.Printf("Number %v found at index: %v\n", 4, index)
Search with custom function
The function sort.Search(n int, f (int) bool) works different compared to search functions in most other programming languages:
- It returns the smallest index in the range [0, n at which f(i) returns true
- For n = 10 sort.Search will call f with values between 0, 9
- To find an element out of all slice elements n equals typically len(slice)
- It returns n if f wasn’t true for any index in the range [0, n]
- The function f is typically a closure
- for ascending sorted slices f(i) should return true if slice[i] >= value to be searched for
- for descending sorted slices f(i) should return true if slice[i] <= value to be searched for
Again: sort.Search returns the smallest index with a value >= (ascending) or <= (descending) than the searched value. There is no guaranty that this is an exact match. To find an exact match, you have to test slice[index] == value.
package main
import (
"fmt"
"sort"
)
func main() {
products := []struct {
Name string
Price int
}{
{"Microphone", 10},
{"Mouse", 20},
{"Keyboard", 20},
{"Headphone", 15},
}
searchFor := 20
// ascending sort and search
sort.Slice(products, func(i, j int) bool {
return products[i].Price < products[j].Price
})
fmt.Println(products)
// [{Microphone 10} {Headphone 15} {Mouse 20} {Keyboard 20}]
index := sort.Search(len(products), func(i int) bool {
return products[i].Price >= searchFor
})
// Test for exact match
if index < len(products) && products[index].Price == searchFor {
fmt.Printf("Product with price %v found at index: %v\n", searchFor, index)
//Product with price 20 found at index: 2
} else {
fmt.Printf("Value not found\n")
}
// descending sort and search
sort.Slice(products, func(i, j int) bool {
return products[i].Price > products[j].Price
})
fmt.Println(products)
// [{Mouse 20} {Keyboard 20} {Headphone 15} {Microphone 10}]
index = sort.Search(len(products), func(i int) bool {
return products[i].Price <= searchFor
})
if index < len(products) && products[index].Price == searchFor {
fmt.Printf("Product with price %v found at index: %v\n", searchFor, index)
//Product with price 20 found at index: 2
} else {
fmt.Printf("Value not found\n")
}
}