DMarlon Studio

Lesson 10 · 10 of 20

sort.Slice and sort.Interface · Go

Sort structs in Go with sort.Slice, sort.SliceStable and sort.Interface, flip the order with sort.Reverse and search with sort.Search.

Go · sort.Slice and sort.Interface in DMarlon Studio: the example's code and its output in the terminal

The sort package sorts in two ways. The classic one is `sort.Interface`: a type with three methods, Len, Less and Swap. ByName is a []Product under another name with those three methods; sort.Sort(ByName(ps)) converts the slice (without copying it: they share the data) and sorts it by name.

The quick way is `sort.Slice`: it takes the slice and a less(i, j) function that says whether item i goes before item j. The closure uses ps directly, so sorting by price is one line, with no type to declare.

With the slice sorted by price, `sort.Search` does a binary search: it returns the first index where the condition is true, here the first product costing 30 EUR or more, Lamp. It only works if the slice is sorted by what you are searching for; if nothing matches, it returns len(ps).

sort.Slice does not guarantee the order of equal items. `sort.SliceStable` does: when sorting by category, the earlier price order is kept inside office and tech (Lamp before Desk, Cable before Mouse). That is how you sort by several keys: the secondary one first, then the main one, stably.

`sort.Reverse` wraps any sort.Interface and flips Less: reverse alphabetical order without writing another type. The String method on Product (from the Stringer lesson) makes %v print each product as Mouse/tech/25.

Code · main.go

package mainimport (	"fmt"	"sort")type Product struct {	Name, Category string	Price          float64}// ByName implements sort.Interface: Len, Less and Swap.type ByName []Productfunc (p ByName) Len() int           { return len(p) }func (p ByName) Less(i, j int) bool { return p[i].Name < p[j].Name }func (p ByName) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }func (p Product) String() string { return fmt.Sprintf("%s/%s/%.0f", p.Name, p.Category, p.Price) }func main() {	ps := []Product{{"Mouse", "tech", 25}, {"Desk", "office", 180}, {"Lamp", "office", 40}, {"Cable", "tech", 9}}	sort.Sort(ByName(ps))	fmt.Printf("%-12s%v\n", "by name", ps)	sort.Slice(ps, func(i, j int) bool { return ps[i].Price < ps[j].Price })	fmt.Printf("%-12s%v\n", "by price", ps)	i := sort.Search(len(ps), func(i int) bool { return ps[i].Price >= 30 }) // binary search	fmt.Println("first from 30 EUR:", ps[i].Name)	sort.SliceStable(ps, func(i, j int) bool { return ps[i].Category < ps[j].Category })	fmt.Printf("%-12s%v\n", "by category", ps) // stable: keeps the price order inside each category	sort.Sort(sort.Reverse(ByName(ps)))	fmt.Printf("%-12s%v\n", "name desc", ps)}

Output · go run main.go

by name     [Cable/tech/9 Desk/office/180 Lamp/office/40 Mouse/tech/25]
by price    [Cable/tech/9 Mouse/tech/25 Lamp/office/40 Desk/office/180]
first from 30 EUR: Lamp
by category [Lamp/office/40 Desk/office/180 Cable/tech/9 Mouse/tech/25]
name desc   [Mouse/tech/25 Lamp/office/40 Desk/office/180 Cable/tech/9]

Key points

  • sort.Interface needs Len, Less and Swap.
  • sort.Slice sorts with a less(i, j) function.
  • sort.SliceStable keeps equal items in their order.
  • sort.Reverse flips any sort.Interface.
  • sort.Search searches a slice that is already sorted.

Exercise

Sort the products by category and, within each category, by name, with a single sort.Slice call whose less compares Category first and, if they match, Name.