关于sorting:Go:有没有一种方法可以避免执行完整sort.Interfaces的切片结构?

 2020-01-14 

Go: Is there a way to avoid the implementation of the full sort.Interface for slices of structs?

如果我在Go中有一个数组的结构体/切片,并想使用sort包对它们进行排序,在我看来,我需要实现包含3个方法的整个sort接口:

  • 交换

无论数组中的struct类型如何,Len和Swap似乎始终应该具有相同的实现。

有没有一种方法可以避免每次都使用工具Len和Swap,或者这仅仅是Go语言中缺少泛型的限制?


如果您要对同一切片类型实施几种不同的比较操作,则可以使用嵌入来避免每次重新定义Len和Swap。您还可以使用此技术将参数添加到排序中,例如根据某些运行时值进行反向排序或不进行排序。

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import (
   "sort"
)

type T struct {
    Foo int
    Bar int
}

// TVector is our basic vector type.
type TVector []T

func (v TVector) Len() int {
    return len(v)
}

func (v TVector) Swap(i, j int) {
    v[i], v[j] = v[j], v[i]
}

// default comparison.
func (v TVector) Less(i, j int) bool {
    return v[i].Foo < v[j].Foo
}

// TVectorBarOrdered embeds TVector and overrides
// its Less method so that it is ordered by the Bar field.
type TVectorBarOrdered struct {
    TVector
}

func (v TVectorBarOrdered) Less(i, j int) bool {
    return v.TVector[i].Bar < v.TVector[j].Bar
}

// TVectorArbitraryOrdered sorts in normal or reversed
// order depending on the order of its Reversed field.
type TVectorArbitraryOrdered struct {
    Reversed bool
    TVector
}

func (v TVectorArbitraryOrdered) Less(i, j int) bool {
    if v.Reversed {
        i, j = j, i
    }
    return v.TVector[i].Foo < v.TVector[j].Foo
}

func main() {
    v := []T{{1, 3}, {0, 6}, {3, 2}, {8, 7}}
    sort.Sort(TVector(v))
    sort.Sort(TVectorBarOrdered{v})
    sort.Sort(TVectorArbitraryOrdered{true, v})
}


您自己的答案是正确的。对于数组或切片,Len()和Swap()的实现很简单。像len()一样,Go可以在此处提供一个本机swap()。但是现在使用的接口也可以用于更复杂的数据结构,例如BTrees。它仍然允许Sort()函数正常工作(就像我的并行quicksort一样,它使用相同的sort接口)。


如果要对切片进行排序(Len和Swap始终具有相同的实现),那么sort包现在具有仅需要实现Less的功能:

func Slice(切片接口{},少了func(i,j int)bool)


尽管这是一个古老的问题,但我想指出
github.com/bradfitz/slice
包。
但是,仅作为示例或概念证明,我不建议您实际使用它(它以"总"一词记录):

It uses gross, low-level operations to make it easy to sort arbitrary slices with only a less function, without defining a new type with Len and Swap operations.

在实际代码中,我发现执行以下操作完全无关紧要,快速,简短,易读且分心:

1
2
3
4
5
6
7
type points []point

func (p []points) Len() int      { return len(p) }
func (p []points) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p []points) Less(i, j int) bool {
        // custom, often multi-line, comparison code here
}

在这里gofmt坚持typefunc行之间的空白行
但它没有问题
多个单行功能,无空行
并且很好地将功能主体排列在一起。
对于这样的事情,我发现这是一个很好的可读紧凑形式。

至于您的评论:

It seems that Len and Swap should always have the same implementation no matter the type of struct is in the [slice]

就在前一周,我需要一种将多个切片中的成对元素保持在一起(用于输入strings.NewReplacer)的方法,该方法需要一些琐碎的变化,例如:

1
2
3
4
5
6
7
8
type pairByLen []string

func (p pairByLen) Len() int           { return len(p) / 2 }
func (p pairByLen) Less(i, j int) bool { return len(p[i*2]) > len(p[j*2]) }
func (p pairByLen) Swap(i, j int) {
        p[i*2], p[j*2] = p[j*2], p[i*2]
        p[i*2+1], p[j*2+1] = p[j*2+1], p[i*2+1]
}

类似于github.com/bradfitz/slice中的接口,该接口不支持此功能。
再次,我发现此布局简单,紧凑且可读。
尽管(在这种情况下可能更多),其他人可能会不同意。