During a recent project at work, I encountered an interesting challenge: implementing multi-condition sorting for a complex data structure. While I was familiar with this concept from SQL queries using ORDER BY clauses, I had never implemented it programmatically before. After some careful consideration, I developed a solution in Go, which I later had to port to PHP (though unfortunately without the benefit of generics support 🥲).
I started by implementing a generic struct to hold the data I am sorting along with the comparison functions:
type Sorter[T any] struct {
items []T
less []func(a, b T) bool
}
Then, I implemented the Len
, Swap
and Less
to implement the sort.Interface
and be able to use the regular
methods from the sort
package:
func (ms Sorter[T]) Len() int {
return len(ms.items)
}
func (ms Sorter[T]) Swap(i, j int) {
ms.items[i], ms.items[j] = ms.items[j], ms.items[i]
}
func (ms Sorter[T]) Less(i, j int) bool {
for _, less := range ms.less {
switch {
case less(ms.items[i], ms.items[j]):
return true
case less(ms.items[j], ms.items[i]):
return false
}
}
return false
}
Finally, I implemented a helper function to wrap the custom Sorter
type:
func OrderedBy[T any](items []T, less ...func(T, T) bool) *Sorter[T] {
return &Sorter[T]{
items: items,
less: less,
}
}
Then, it was a matter of using it. It can be done as follows:
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
City string
}
// Gimme a bunch of people
people := []Person{
{"Bob", 25, "London"},
{"Charlie", 30, "London"},
{"Alice", 25, "New York"},
{"David", 20, "Paris"},
{"Bob", 25, "Berlin"},
}
// Define some ordering criteria
byAge := func(a, b Person) bool { return a.Age < b.Age }
byName := func(a, b Person) bool { return a.Name < b.Name }
byCity := func(a, b Person) bool { return a.City < b.City }
// Sort first by age, then name, then city, in ascending order
sorter := OrderedBy(people, byAge, byName, byCity)
sort.Sort(sorter)
fmt.Println(people)
// [{David 20 Paris} {Alice 25 New York} {Bob 25 London} {Charlie 30 London}]
It was a very interesting exercise, and I honestly had a lot of fun doing it.