There is one problem bothering me in Golang. Say I have 2 structs:
type Dog struct {
Name string
Breed string
Age int
}
type Cat struct {
Name string
FavoriteFood string
Age int
}
And when I try to sort []*Dog
and []*Cat
by Age
, I have to define 2 different sort struct like:
type SortCat []*Cat
func (c SortCat) Len() int {//..}
func (c SortCat) Swap(i, j int) {//..}
func (c SortCat) Less(i, j int) bool {//..}
type SortDog []*Dog
func (c SortDog) Len() int {//..}
func (c SortDog) Swap(i, j int) {//..}
func (c SortDog) Less(i, j int) bool {//..}
A natural thought is to implement some SortableByAge
interface and create a Less
function using the interface function. Like:
type SortableByAge interface {
AgeValue() int
}
And then:
type SortAnimal []SortableByAge
func (c SortDog) Less(i, j int) bool {
return c[i].AgeValue() < c[j].AgeValue()
}
However, according to: http://golang.org/doc/faq#convert_slice_of_interface
dogs := make([]*Dogs, 0 , 1)
//add dogs here
sort.Sort(SortAnimal(dogs))
Above is not possible.
So I wonder what is the best practice for this case and
Is there any other technique that can reduce the need for implementing the sort.Interface
for similar structs again and again that I have missed?
EDIT: I realized that my examples are terrible :(
In real life case, these 2 structs are very different, the only thing in common between them is that I wish to sort them by a common numeric value.
A better example would be:
type Laptop {//...}
type Pizza {//...}
And the only thing these 2 structs share in common is that I wish to sort a slice(agh... should not have used Pizza
in example) of them by price.
So, combining them to a common struct is not really working for a lot of cases. But will look into go generate.
In this specific case you shouldn't use 2 different types as they are identical, just use a common Animal
type:
type Animal struct {
Name string
Age int
}
func (a Animal) String() string { return fmt.Sprintf("%s(%d)", a.Name, a.Age) }
type SortAnim []*Animal
func (c SortAnim) Len() int { return len(c) }
func (c SortAnim) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c SortAnim) Less(i, j int) bool { return c[i].Age < c[j].Age }
func main() {
dogs := []*Animal{&Animal{"Max", 4}, &Animal{"Buddy", 3}}
cats := []*Animal{&Animal{"Bella", 4}, &Animal{"Kitty", 3}}
fmt.Println(dogs)
sort.Sort(SortAnim(dogs))
fmt.Println(dogs)
fmt.Println(cats)
sort.Sort(SortAnim(cats))
fmt.Println(cats)
}
Output (Go Playground):
[Max(4) Buddy(3)]
[Buddy(3) Max(4)]
[Bella(4) Kitty(3)]
[Kitty(3) Bella(4)]
In general you can only use a common sorting implementation if you're willing to give up concrete types and use interface types instead.
Create the interface type you want your slice to hold:
type Animal interface {
Name() string
Age() int
}
You can have a common implementation of this:
type animal struct {
name string
age int
}
func (a *animal) Name() string { return a.name }
func (a *animal) Age() int { return a.age }
func (a animal) String() string { return fmt.Sprintf("%s(%d)", a.name, a.age) }
Your specific animal types:
type Dog struct {
animal // Embed animal (its methods and fields)
}
type Cat struct {
animal // Embed animal (its methods and fields)
}
You implement sort.Interface
on SortAnim
:
type SortAnim []Animal
func (c SortAnim) Len() int { return len(c) }
func (c SortAnim) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c SortAnim) Less(i, j int) bool { return c[i].Age() < c[j].Age() }
Using it:
dogs := SortAnim{&Dog{animal{"Max", 4}}, &Dog{animal{"Buddy", 3}}}
cats := SortAnim{&Cat{animal{"Bella", 4}}, &Cat{animal{"Kitty", 3}}}
fmt.Println(dogs)
sort.Sort(SortAnim(dogs))
fmt.Println(dogs)
fmt.Println(cats)
sort.Sort(SortAnim(cats))
fmt.Println(cats)
Output (Go Playground):
[Max(4) Buddy(3)]
[Buddy(3) Max(4)]
[Bella(4) Kitty(3)]
[Kitty(3) Bella(4)]
Note: as illustrated in commit ad26bb5, in Go 1.8 (Q1 2017), you won't have to implement Len()
and Swap()
and Less()
since issue 16721 was resolved. Only Less()
is needed, the rest is done by reflection.
The problem was:
- Vast majority of sort.Interface uses a slice
- Have to define a new top level type
Len
andSwap
methods are always the same- Want to make common case simpler with least hit to performance
See the new sort.go
:
// Slice sorts the provided slice given the provided less function.
//
// The sort is not guaranteed to be stable. For a stable sort, use
// SliceStable.
//
// The function panics if the provided interface is not a slice.
func Slice(slice interface{}, less func(i, j int) bool) {
rv := reflect.ValueOf(slice)
swap := reflect.Swapper(slice)
length := rv.Len()
quickSort_func(lessSwap{less, swap}, 0, length, maxDepth(length))
}
So as long as you have a Less()
function comparing two instances respecting an interface, you can sort any number of struct respecting said common interface.
Go 1.18 generics will add another option, as illustrated in nwillc/genfuncs/container/sort.go
.
// Sort sorts a slice by the LessThan order.
func (s GSlice[T]) Sort(lessThan genfuncs.BiFunction[T, T, bool]) {
n := len(s)
s.quickSort(0, n, maxDepth(n), lessThan)
}
With BiFunction
:
// BiFunction accepts two arguments and produces a result.
type BiFunction[T, U, R any] func(T, U) R
This is just an example illustrating how a generic sort could be implemented: it is not an official one (since generics are, at the ime of writing, Q1 2022, still very new).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With