如何避免重新实现sort.Interface类似的golang结构

时间:2015-05-19 21:39:03

标签: go interface coding-style slice

Golang有一个问题困扰我。 说我有2个结构:

type Dog struct {
   Name string
   Breed string
   Age int
}

type Cat struct {
    Name string
    FavoriteFood string
    Age int
}

当我尝试按[]*Dog[]*CatAge进行排序时,我必须定义2个不同的排序结构,如:

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 {//..}

一个自然的想法是实现一些SortableByAge接口并使用接口函数创建一个Less函数。像:

type SortableByAge interface {
    AgeValue() int
}

然后:

type SortAnimal []SortableByAge
func (c SortDog) Less(i, j int) bool {
    return c[i].AgeValue() < c[j].AgeValue() 
}

然而,根据: http://golang.org/doc/faq#convert_slice_of_interface

dogs := make([]*Dogs, 0 , 1)
//add dogs here
sort.Sort(SortAnimal(dogs))

以上是不可能的。

所以我想知道这个案例的最佳做法是什么,

是否有其他技术可以减少我一次又一次地忽略对类似结构实施sort.Interface的需要?

编辑: 我意识到我的例子太可怕了:(

在现实生活中,这两个结构是非常不同的,它们之间唯一的共同点是我希望用一个通用的数值对它们进行排序。

更好的例子是:

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

这两个结构的唯一共同之处在于,我希望按价格对它们进行排序(agh ...不应该在示例中使用Pizza)。

因此,将它们组合到一个通用结构中并不适用于很多情况。 但是会考虑去生成。

3 个答案:

答案 0 :(得分:11)

此具体案例

在这种特定情况下,您不应该使用两种不同的类型,因为它们是相同的,只需使用通用的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)
}

输出(Go Playground):

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

一般案例

一般情况下,如果您愿意放弃具体类型并使用接口类型,则只能使用通用排序实现。

创建希望切片保留的界面类型:

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

你可以有一个共同的实现:

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

您的特定动物类型:

type Dog struct {
    animal  // Embed animal (its methods and fields)
}

type Cat struct {
    animal // Embed animal (its methods and fields)
}

您在sort.Interface上实施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() }

使用它:

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)

输出(Go Playground):

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

答案 1 :(得分:6)

注意:如commit ad26bb5所示,在Go 1.8(2017年第1季度)中,您不必实施Len()Swap()以及Less(),因为{{3已经解决了。只需要Less(),剩下的就是反思。

问题是:

  
      
  1. 大多数sort.Interface使用切片
  2.   
  3. 必须定义新的顶级类型
  4.   
  5. LenSwap方法始终相同
  6.   
  7. 希望通用案例更简单,性能最低
  8.   

请参阅issue 16721

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

因此,只要您有一个Less()函数来比较两个关于接口的实例,就可以对任何数量的结构进行排序,这些结构与所述公共接口相关。

答案 2 :(得分:0)

此案例的最佳做法是定义

type Animal struct{
    Species,Name string
    Age int
}

正如twotwotwo所建议的那样。如果猫和狗的相似性足以以相同的方式排序,它们也足够相似,可以是相同的结构。如果它们在某种程度上不同,那么您应该为每种类型重新实现接口。

另一种方法是将所有指针从[]*Cat切片复制到相同大小的[]SortableByAge切片中。如果要对切片进行排序,则需要O(n * log(n)),因此额外的O(n)不应该是性能问题。

第三种选择,在极少数情况下,由于某种原因,您有许多类型必须是不同但仍具有非常简单的排序功能,您可以查看使用go generate自动生成它们。