I have an array/slice of members:
type Member struct {
Id int
LastName string
FirstName string
}
var members []Member
My quest
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) }
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