【问题标题】:How to avoid re-implementing sort.Interface for similar golang structs如何避免重新实现类似 golang 结构的 sort.Interface
【发布时间】:2015-07-31 22:19:09
【问题描述】:

在 Golang 中有一个问题困扰着我。 假设我有 2 个结构:

type Dog struct {
   Name string
   Breed string
   Age int
}

type Cat struct {
    Name string
    FavoriteFood string
    Age int
}

当我尝试通过Age 对[]*Dog 和[]*Cat 进行排序时,我必须定义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 {//...}

这两个结构的唯一共同点是我希望按价格对它们中的一个切片进行排序(啊……不应该在示例中使用Pizza)。

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

【问题讨论】:

  • 我建议重新实现它,因为它只有三行。
  • 既然没有泛型 -> 重新实现
  • (另一件事是如果它们真的相似,也许你真的想要type Animal { Species, Name string; Age int }。并非所有不同的真实世界类别都必须映射到你的不同类型程序。)
  • 谢谢@twotwotwo !我认为我的例子很糟糕,在实际项目中,这两个结构在很多方面都不同,我只是希望它们都可以按数字字段排序,我首先想到的是接口,但发现它不支持.所以只要检查一下stackoverflow,以防我错过了什么

标签: go interface coding-style slice


【解决方案1】:

这个具体案例

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

你在SortAnim上实现sort.Interface:

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

【讨论】:

    【解决方案2】:

    注意:如 commit ad26bb5 中所示,在 Go 1.8(2017 年第一季度)中,您不必实现 Len() 和 Swap() 和 Less(),因为 issue 16721 已解决。只需要Less(),其余通过反射完成。

    问题是:

    1. 绝大多数 sort.Interface 使用切片
    2. 必须定义一个新的顶级类型
    3. Len 和 Swap 方法始终相同
    4. 希望使常见案例更简单,同时对性能的影响最小

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

    因此,只要您有一个 Less() 函数比较尊重一个接口的两个实例,您就可以对尊重所述公共接口的任意数量的结构进行排序。

    【讨论】:

    • 另外,您只需创建一个 Less(匿名)lambda。 sort.Slice(a, func(i, j int) bool { return a[i] &lt; a[j] }) 这将按升序排序,如果您想要相反,只需在 lambda 中写入 a[i] &gt; a[j]。
    【解决方案3】:

    这种情况下的最佳实践是定义

    type Animal struct{
        Species,Name string
        Age int
    }
    

    正如 twotwotwo 所建议的那样。如果 cat 和 dog 足够相似,可以以相同的方式排序,那么它们也足够相似,可以成为相同的结构。如果它们在某些方面有所不同,那么您应该为每种类型重新实现接口。

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

    第三种选择,如果您有许多类型由于某种原因必须是不同的但仍然具有非常简单的排序功能,您可以使用go generate 自动生成它们。

    【讨论】:

    • 谢谢@johan,我也对 go generate 感兴趣!您能否详细说明一下在这种情况下我们如何使用 go generate?
    猜你喜欢
    • 2020-10-10
    • 2011-06-24
    • 2021-07-08
    • 1970-01-01
    • 2012-07-11
    • 1970-01-01
    • 2015-12-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多