Avoiding re-implementing sort.Interface for similar golang structures

There is one problem bothering me in the Golang. Let's say I have 2 structures:

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 sorting structures, for example:

 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 an interface function. as:

 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)) 

Higher is impossible.

So I wonder what is best for this case and

Is there another method that can reduce the need to implement sort.Interface for similar structures over and over, which I missed?

EDIT: I realized my examples are terrible :(

In real life, these two structures are very different, the only thing between them is that I want to sort them using a common numerical value.

The best example:

 type Laptop {//...} type Pizza {//...} 

And the only thing that unites these 2 structures is that I want to sort the slice (yeah ... I shouldn't have used Pizza in the example) from them by price.

Thus, combining them into a common structure does not actually work in many cases. But let's see how to generate it.

+5
source share
3 answers

This particular case

In this particular case, you should not use 2 different types, since they are identical, just use the generic type Animal :

 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) } 

Conclusion ( Go to the playground ):

 [Max(4) Buddy(3)] [Buddy(3) Max(4)] [Bella(4) Kitty(3)] [Kitty(3) Bella(4)] 

General case

In general, you can only use the general sorting implementation if you want to discard specific types and use interface types.

Create the type of interface you want your fragment to hold on to:

 type Animal interface { Name() string Age() int } 

You may have a general implementation:

 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 types of animals:

 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:

 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) 

Conclusion ( Go to the site ):

 [Max(4) Buddy(3)] [Buddy(3) Max(4)] [Bella(4) Kitty(3)] [Kitty(3) Bella(4)] 
+7
source

Note: as shown in commit ad26bb5 , in Go 1.8 (Q1 2017) you do not have to implement Len() and Swap() and Less() , since the problem 16721 has been resolved. Only Less() required, the rest is reflection.

The problem was this:

  • Most sort.Interface uses a slice.
  • Define a new top level type
  • Len and Swap methods are always the same.
  • Want to make the usual case easier with the least impact on 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, if you have a Less() function comparing two instances related to an interface, you can sort any number of structures corresponding to the specified common interface.

+5
source

The best practice for this case would be to identify

 type Animal struct{ Species,Name string Age int } 

as suggested in a double way. If the cat and dog are similar enough to be sorted the same way, they are also similar enough to be the same structure. If they are somehow different from each other, then you must redefine the interface for each type.

An alternative would be to copy all the pointers from your []*Cat fragment to the []SortableByAge the same size. If you are going to sort a slice, it will take O (n * log (n)), so additional O (n) should not be a performance issue.

The third alternative, in rare cases when you have many types that for some reason should be different, but still have very simple sorting functions, you can look at their auto-generation using go generate .

0
source

All Articles