英文:
Is there a way to avoid the implementation of the full sort.Interface for slices of structs?
问题
如果我在Go中有一个结构体的数组/切片,并且想要使用sort包对它们进行排序,似乎我需要实现整个排序接口,其中包含3个方法:
- Len
- Swap
- Less
似乎无论数组中的结构体类型如何,Len和Swap应该始终具有相同的实现。
有没有办法避免每次都实现Len和Swap,或者这只是Go中缺乏泛型的限制?
英文:
If I have an array/slice of structs in Go and want to sort them using the sort package it seems to me that I need to implement the whole sort interface which contains 3 methods:
- Len
- Swap
- Less
It seems that Len and Swap should always have the same implementation no matter the type of struct is in the array.
Is there a way to avoid having the implement Len and Swap every time or is this just a limitation from the lack of Generics in Go?
答案1
得分: 8
如果您正在对相同的切片类型实现多个不同的比较操作,您可以使用嵌入来避免每次重新定义Len和Swap。您还可以使用此技术向排序添加参数,例如根据某个运行时值进行反向排序或不进行排序。
例如:
package main
import (
"sort"
)
type T struct {
Foo int
Bar int
}
// TVector是我们的基本向量类型。
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]
}
// 默认比较。
func (v TVector) Less(i, j int) bool {
return v[i].Foo < v[j].Foo
}
// TVectorBarOrdered嵌入TVector并覆盖其Less方法,以便按Bar字段排序。
type TVectorBarOrdered struct {
TVector
}
func (v TVectorBarOrdered) Less(i, j int) bool {
return v.TVector[i].Bar < v.TVector[j].Bar
}
// TVectorArbitraryOrdered根据其Reversed字段的顺序以正常或反向顺序进行排序。
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})
}
英文:
If you are implementing several different comparison operations on the same slice type, you can use embedding to avoid redefining Len and Swap each time. You can also use this technique to add parameters to the sort, for example to sort in reverse or not depending on some run-time value.
e.g.
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})
}
答案2
得分: 5
你自己的答案是正确的。在数组或切片的情况下,Len()和Swap()的实现很简单。就像len()一样,Go语言可以在这里提供一个本地的swap()函数。但是现在使用的接口也可以用于更复杂的数据结构,比如B树。它仍然允许Sort()函数工作(就像我的并行快速排序算法,它使用相同的排序接口)。
英文:
Your own answer is right. In your case of an array or slice the implementations of Len() and Swap() are simple. Like len() Go could provide a native swap() here. But the interface which is used now can also be used for more complex data structures like BTrees. It still allows the Sort() function to work (like my parallel quicksort, which uses the same sort interface).
答案3
得分: 2
如果你想对切片进行排序(其中Len和Swap始终具有相同的实现),sort包现在有一个只需要实现Less函数的函数:
func Slice(slice interface{}, less func(i, j int) bool)
英文:
If you want to sort slices (for which Len and Swap always have the same implementation), the sort package now has a function that only requires an implementation of Less:
答案4
得分: 0
虽然这是一个老问题,但我想指出github.com/bradfitz/slice
包。但作为一个示例或概念验证,我不建议实际使用它(它的文档中有“gross”一词):
它使用粗糙的低级操作,使得只需一个less函数就能轻松对任意切片进行排序,而无需定义一个具有Len和Swap操作的新类型。
在实际代码中,我发现这完全是微不足道的、快速的、简短的、可读的,而且不会分散注意力,只需像这样做:
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 {
// 自定义的、通常是多行的比较代码在这里
}
这里,gofmt
坚持在type
和func
行之间有一个空行,但对于没有空行的多个单行函数,它没有问题,并且它很好地对齐了函数体。我发现这是一种对于这种情况来说既简洁又易读的形式。
至于你的评论:
看起来无论切片中的结构类型如何,Len和Swap应该始终具有相同的实现
就在上周,我需要对切片中的元素进行排序,以便将它们成对地保持在一起(用于输入到strings.NewReplacer
),这需要一个微小的变化,如下所示:
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
中的接口中是不支持的。同样,我发现这种布局简单、紧凑且易读。尽管(也许在这种情况下更是如此),其他人可能持不同意见。
英文:
Although this is an old question, I'd like to point out
the github.com/bradfitz/slice
package.
But as an example or proof of concept only, I would not recommend this actually be used (it's documented with the word "gross"):
> 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.
In actual code, I find it completely trivial, quick, short, readable, and non-distracting to just do something like:
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
}
Here gofmt
insists on a blank line between the type
and func
lines
but it has no problem with
multiple one-line functions with no blank lines
and it nicely lines up the function bodies.
I find this a nice readable compact form for such things.
As for your comment that:
> It seems that Len and Swap should always have the same implementation no matter the type of struct is in the [slice]
just the other week I need a sort that kept pairs of elements in a slice together (for input to strings.NewReplacer
) that required a trivial variation like:
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]
}
This is not supported by an interface like the one in github.com/bradfitz/slice
.
Again, I find this layout easy, compact, and readable.
Although (perhaps more so in this case), others may disagree.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论