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 stri
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)]
The best practice for this case would be to define
type Animal struct{
Species,Name string
Age int
}
as suggested by twotwotwo. If cat and dog are similar enough to be sorted in the same way, they are also similar enough to be the same struct. If they are different in some way, then you should reimplement the interface for each type.
An alternative could be to copy all pointers from your []*Cat
slice into a []SortableByAge
slice of the same size. If you are going to sort the slice, that will take O(n*log(n)) so an extra O(n) shouldn't be a performance issue.
A third alternative, in the rare event that you have many types that for some reason have to be distinct but still have very simple sorting functions, you can look at autogenerating them with go generate.
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.