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

问题描述 投票:0回答:4

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

type Dog struct {
   Name string
   Breed string
   Age int
}

type Cat struct {
    Name string
    FavoriteFood string
    Age int
}

当我尝试按

[]*Dog
[]*Cat
Age
进行排序时,我必须定义 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
)进行排序。

因此,将它们组合到一个通用结构中对于很多情况来说并不真正有效。 但会研究 go generated。

go interface coding-style slice
4个回答
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)
}

输出(去游乐场):

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

输出(去游乐场):

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

10
投票

注意:如 commit ad26bb5 中所示,在 Go 1.8(2017 年第 1 季度)中,由于

issue 16721
已解决,您不必实现
Len()
Swap()
Less()。只需要
Less()
,剩下的都是通过反射来完成。

问题是:

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

查看

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()
函数来比较尊重某个接口的两个实例,您就可以对尊重所述公共接口的任意数量的结构进行排序。


Go 1.18 generics 将添加另一个选项,如

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

BiFunction

// BiFunction accepts two arguments and produces a result.
type BiFunction[T, U, R any] func(T, U) R

这只是说明如何实现泛型排序的示例:它不是官方的(因为泛型在撰写本文时是 2022 年第一季度,仍然非常新)。


0
投票

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

type Animal struct{
    Species,Name string
    Age int
}

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

另一种方法是将

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

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


0
投票

在 Go 1.21 中,他们添加了一个新包 slices。它包含处理泛型类型切片的函数。

slices.SortFunc()
可能会为您简化事情。与 sort.Sort() 相比,它更快且
官方更受欢迎。

package main import ( "cmp" "fmt" "slices" ) type Dog struct { Name string Breed string Age int } type Cat struct { Name string FavoriteFood string Age int } func CmpDog(a, b Dog) int { return cmp.Compare(a.Age, b.Age) } func CmpCat(a, b Cat) int { return cmp.Compare(a.Age, b.Age) } func main() { dogs := []Dog{ {"Candice", "Poodle", 13}, {"Alice", "Dalmation", 4}, {"Bob", "Labrador", 8}, } cats := []Cat{ {"Betty", "Fish", 108}, {"Brittany", "Fish", 99}, {"Karen", "Fish", 81}, } slices.SortFunc(dogs, CmpDog) slices.SortFunc(cats, CmpCat) }
请注意,

slices.SortFunc()

 不稳定
。为了稳定排序,您可以使用 slices.SortStableFunc(),它与
slices.SortFunc()
具有相同的签名。
    

© www.soinside.com 2019 - 2024. All rights reserved.