I have an array/slice of members:
type Member struct {
Id int
LastName string
FirstName string
}
var members []Member
My question is how to sort them by LastName and then by FirstName.
I have an array/slice of members:
type Member struct {
Id int
LastName string
FirstName string
}
var members []Member
My question is how to sort them by LastName and then by FirstName.
In Go 1.22 and later, use slices.SortFunc, cmp.Or and cmp.Compare to write a concise and readable sort:
slices.SortFunc(members, func(a, b Member) int {
return cmp.Or(
cmp.Compare(a.LastName, b.LastName),
cmp.Compare(a.FirstName, b.FirstName),
)
})
Use the sort.Slice (available since Go 1.8) or the sort.Sort function to sort a slice of values.
With both functions, the application provides a function that tests if one slice element is less than another slice element. To sort by last name and then first name, compare last name and then first name:
// If last names are different, then use last
// name to determine whether element i is less than
// element j.
if members[i].LastName != members[j].LastName {
return members[i].LastName < members[j].LastName
}
// Otherwise, use first name to determine whether
// element i is less than element j.
return members[i].FirstName < members[j].FirstName
The less function is specified using an anonymous function with sort.Slice:
var members []Member
sort.Slice(members, func(i, j int) bool {
if members[i].LastName != members[j].LastName {
return members[i].LastName < members[j].LastName
}
return members[i].FirstName < members[j].FirstName
})
The less function is specified with through an interface with the sort.Sort function:
type byLastFirst []Member
func (members byLastFirst) Len() int { return len(members) }
func (members byLastFirst) Swap(i, j int) { members[i], members[j] = members[j], members[i] }
func (members byLastFirst) Less(i, j int) bool {
if members[i].LastName != members[j].LastName {
return members[i].LastName < members[j].LastName
}
return members[i].FirstName < members[j].FirstName }
sort.Sort(byLastFirst(members))
Unless performance analysis shows that sorting is a hot spot, use the approach that's most convenient for your application.
Use the newer sort.Slice function as such:
sort.Slice(members, func(i, j int) bool {
switch strings.Compare(members[i].FirstName, members[j].FirstName) {
case -1:
return true
case 1:
return false
}
return members[i].LastName > members[j].LastName
})
or something like that.
sort.Stable or sort.SliceStable.strings.Compare. It is no different than doing == and <, which are both required in multi-valued comparisons.Another pattern, which I find slightly cleaner:
if members[i].LastName != members[j].LastName {
return members[i].LastName < members[j].LastName
}
return members[i].FirstName < members[j].FirstName
The shortest and still comprehensible code I managed to write for this is:
package main
import (
"fmt"
"sort"
)
type Member struct {
Id int
LastName string
FirstName string
}
func sortByLastNameAndFirstName(members []Member) {
sort.SliceStable(members, func(i, j int) bool {
mi, mj := members[i], members[j]
switch {
case mi.LastName != mj.LastName:
return mi.LastName < mj.LastName
default:
return mi.FirstName < mj.FirstName
}
})
}
The pattern using the switch statement easily extends to more than two sorting criteria and is still short enough to be read.
Here's the rest of the program:
func main() {
members := []Member{
{0, "The", "quick"},
{1, "brown", "fox"},
{2, "jumps", "over"},
{3, "brown", "grass"},
{4, "brown", "grass"},
{5, "brown", "grass"},
{6, "brown", "grass"},
{7, "brown", "grass"},
{8, "brown", "grass"},
{9, "brown", "grass"},
{10, "brown", "grass"},
{11, "brown", "grass"},
}
sortByLastNameAndFirstNameFunctional(members)
for _, member := range members {
fmt.Println(member)
}
}
If you want to avoid mentioning the fields LastName and FirstName multiple times and if you want to avoid mixing i and j (which can happen all the way), I played around a bit and the basic idea is:
func sortByLastNameAndFirstNameFunctional(members []Member) {
NewSorter().
AddStr(member -> member.LastName).
AddStr(member -> member.FirstName).
AddInt(member -> member.Id).
SortStable(members)
}
Since Go doesn't support the -> operator for creating anonymous functions and also doesn't have generics like Java, a bit of syntactical overhead is needed:
func sortByLastNameAndFirstNameFunctional(members []Member) {
NewSorter().
AddStr(func(i interface{}) string { return i.(Member).LastName }).
AddStr(func(i interface{}) string { return i.(Member).FirstName }).
AddInt(func(i interface{}) int { return i.(Member).Id}).
SortStable(members)
}
The implementation as well as the API is a bit ugly using interface{} and reflection, but it only mentions each field once, and the application code does not have a single chance to accidentally mix the indexes i and j since it doesn't deal with them.
I designed this API in the spirit of Java's Comparator.comparing.
The infrastructure code for the above sorter is:
type Sorter struct{ keys []Key }
func NewSorter() *Sorter { return new(Sorter) }
func (l *Sorter) AddStr(key StringKey) *Sorter { l.keys = append(l.keys, key); return l }
func (l *Sorter) AddInt(key IntKey) *Sorter { l.keys = append(l.keys, key); return l }
func (l *Sorter) SortStable(slice interface{}) {
value := reflect.ValueOf(slice)
sort.SliceStable(slice, func(i, j int) bool {
si := value.Index(i).Interface()
sj := value.Index(j).Interface()
for _, key := range l.keys {
if key.Less(si, sj) {
return true
}
if key.Less(sj, si) {
return false
}
}
return false
})
}
type Key interface {
Less(a, b interface{}) bool
}
type StringKey func(interface{}) string
func (k StringKey) Less(a, b interface{}) bool { return k(a) < k(b) }
type IntKey func(interface{}) int
func (k IntKey) Less(a, b interface{}) bool { return k(a) < k(b) }
A slightly cleaner solution than the currently-accepted answer is to do something more like this:
sort.Slice(members, func(i, j int) bool {
if members[i].FirstName != members[j].FirstName {
return members[i].FirstName < members[j].FirstName
}
return members[i].LastName < members[j].LastName
})
The nice thing about doing it this way is that it's much clearer how you would extend it if you added a new field Age (or something):
sort.Slice(members, func(i, j int) bool {
if members[i].FirstName != members[j].FirstName {
return members[i].FirstName < members[j].FirstName
}
if members[i].Age != members[j].Age {
return members[i].Age < members[j].Age
}
return members[i].LastName < members[j].LastName
})
So the pattern is that you add an if X[i] != X[j] statement for all properties until the last one, which is just compared normally.
This is how I usually do it:
// You can edit this code!
// Click here and start typing.
package main
import (
"fmt"
"sort"
"strings"
)
type User struct {
Name string
Age int
}
func main() {
users := []User{
{"A", 10},
{"B", 10},
{"C", 20},
{"D", 5},
{"A", 100},
}
sort.Slice(users, func(i, j int) bool {
lhs, rhs := users[i], users[j]
byAge := lhs.Age - rhs.Age
byName := strings.Compare(lhs.Name, rhs.Name) // Returns 0 if equal, -1 if lhs is less than rhs, and 1 if lhs is greater than rhs
// The + sign is not necessary, but adds clarity as it means increasing in value, aka ascending.
// sortBy(+byAge, +byName) // Sort by age asc, by name asc
// sortBy(-byAge, +byName) // Sort by age desc, by name asc
// sortBy(+byName, +byAge) // Sort by name asc, by age asc
return sortBy(-byAge, -byName) // Sort by age desc, by name desc
})
fmt.Println(users)
}
func sortBy(sc ...int) bool {
for _, c := range sc {
if c != 0 {
return c < 0
}
}
return sc[len(sc)-1] < 0
}
This has been very helpful. I needed to sort a slice of structs, and found my answer here. I actually extended it to triply-sorting. Although sorting this much is not optimal for runtime purposes, it's useful in some circumstances, especially when the alternative leads to code that is hard to maintain or modify and where faster runtimes are not crucial.
sort.Slice(servers, func(i, j int) bool {
if servers[i].code < servers[j].code {
return true
} else if servers[i].code > servers[j].code {
return false
} else {
if servers[i].group < servers[j].group {
return true
} else {
if servers[i].group > servers[j].group {
return false
}
}
}
return servers[i].IDGroup < servers[j]. IDGroup
})
This code sorts first by code, then by group, then by IDGroup.
Just created go project to implement it: https://github.com/itroot/keysort
You can just return a list of fields to sort:
package main
import (
"fmt"
"github.com/itroot/keysort"
)
type Data struct {
A int
B string
C bool
}
func main() {
slice := []Data{{1, "2", false}, {2, "2", false}, {2, "1", true}, {2, "1", false}}
keysort.Sort(slice, func(i int) keysort.Sortable {
e := slice[i]
return keysort.Sequence{e.A, e.B, e.C}
})
fmt.Println(slice)
}
Playground link: https://play.golang.org/p/reEDcoXNiwh
[{1 2 false} {2 1 false} {2 1 true} {2 2 false}] where B is sorted 2,1,1,2 which is clearly not correct ordering.ORDER BY A, B, C. If you want to sort by B first, you need to do return keysort.Sequence{e.B, e.A, e.C} - go.dev/play/p/FEdr1ND4Xme, and it will return [{2 1 false} {2 1 true} {1 2 false} {2 2 false}]You can create a slice of functions:
package main
type (
mFunc func(a, b member) bool
member struct { lastName, firstName string }
)
var members = []member{
{"Mullen", "Larry"},
{"Howard", "James"},
{"Clayton", "Adam"},
{"Howard", "Ben"},
}
var mFuncs = []mFunc{
func(a, b member) bool { return a.lastName < b.lastName },
func(a, b member) bool { return a.firstName < b.firstName },
}
and then iterate through the functions, until one of the functions finds a difference:
package main
import (
"fmt"
"sort"
)
func main() {
sort.Slice(members, func(a, b int) bool {
ma, mb := members[a], members[b]
for _, mf := range mFuncs {
if mf(ma, mb) {
return true
}
if mf(mb, ma) {
break
}
}
return false
})
fmt.Println(members) // [{Clayton Adam} {Howard Ben} {Howard James} {Mullen Larry}]
}
Introduced in Go 1.21
import (
"cmp"
"fmt"
"slices"
)
func main() {
type Person struct {
Name string
Age int
}
people := []Person{
{"Gopher", 13},
{"Alice", 55},
{"Bob", 24},
{"Alice", 20},
}
slices.SortFunc(people, func(a, b Person) int {
if n := cmp.Compare(a.Name, b.Name); n != 0 {
return n
}
// If names are equal, order by age
return cmp.Compare(a.Age, b.Age)
})
fmt.Println(people)
}
Just read the code of the SortMultiKeys example in the official Go documentation at https://pkg.go.dev/sort and adapt it to your Member structure.
To keep this answer up to date I won't copy and paste the whole example here, but eventually you'll be able to write it as follows:
OrderedBy(lastName, firstName).Sort(members)
If the requirement changed to sort also by age, you'd simply add the age closure:
OrderBy(lastName, firstName, age).Sort(members)
All good answers, If you don't want to write any configuration. You can use the external package to perform sorting.
go get -d github.com/raunakjodhawat/multisort
And call the function like so:
sortedSlice, err := multisort.MultiSorted(inputSlice, inputKeys, SortOrders)
To look at a concrete example, go to: https://github.com/raunakjodhawat/multisort
Here is a slightly more concise implementation of the accepted answer:
package main
import (
"fmt"
"sort"
)
type Member struct {
FirstName string
LastName string
}
func main() {
members := []Member{
{"John", "Doe"},
{"Jane", "Doe"},
{"Mary", "Contrary"},
}
fmt.Println(members)
sort.Slice(members, func(i, j int) bool {
return members[i].LastName < members[j].LastName || members[i].FirstName < members[j].FirstName
})
fmt.Println(members)
}
Running will print the following output:
[{John Doe} {Jane Doe} {Mary Contrary}]
[{Mary Contrary} {Jane Doe} {John Doe}]
This is as expected: the members are printed in order of ascending last name, where in the case of a tie, they are printed in order of first name.
[{John Doe} {Zach Darren} {Jane Doe} {Mary Contrary}] as [{Mary Contrary} {Jane Doe} {Zach Darren} {John Doe}].members[i].LastName < members[j].LastName || (members[i].LastName == members[j].LastName && members[i].FirstName < members[j].FirstName) => [{Mary Contrary} {Zach Darren} {Jane Doe} {John Doe}]