实现结构体切片的多字段排序
1 简介
本文给出 multiSorter 多字段排序 实现示例,该代码实现了一个可组合多字段排序器,非常类似 SQL ORDER BY:
ORDER BY user ASC, lines DESC, ...

它允许:
动态传入多个比较函数(lessFunc)
按优先级链式比较(逐字段比)
自定义每个字段升序或降序
2 核心设计思想
-
- 支持多字段排序(Primary key、Secondary key …)
Less() 方法中:
for k = 0; k < len(ms.less)-1; k++ {
if less(p,q) return true // p < q
if less(q,p) return false // p > q
// equal → try next field
}
return ms.less[k](p,q)
逐字段比较,直到某字段能决定顺序。
这是多字段排序的通用模式。
内置 sort.Ints、sort.Strings 无法直接实现多字段排序。
-
- 每个字段可以有独立的排序规则
比如:
increasingLines := func(c1,c2) { return c1.lines < c2.lines }
decreasingLines := func(c1,c2) { return c1.lines > c2.lines }
这是 Go 内置 sort 无法直接做到的(尤其是降序排序)。
-
- 排序逻辑可动态组合
例如:
OrderedBy(language, increasingLines, user).Sort(changes)
这类似 SQL:
ORDER BY language ASC, lines ASC, user ASC
相比之下,内置 sort.Slice 的方式一次只能写一个 Less 函数,不支持动态组合多个规则。
-
- 可重用、可扩展
OrderedBy 可用于任意 struct,也可封装到库中复用。 与内置 sort 的优劣对比
功能 sort multiSorter 实现
单字段排序 很方便 可做
多字段排序 需要手写复杂 Less 自动、多字段链式比较
动态组合运行时决定排序字段 不支持 支持
升序/降序混合 需写反转比较逻辑 每个字段可独立决定
代码复用 每个新 struct 要写一个 Less 多字段逻辑高度复用
性能 快 较慢(多次调用 less)
**优势 **
多字段排序的优势在于:
可扩展(多个 lessFunc)
可组合(按需 ORDER BY)
可读性强(类似数据库 ORDER BY)
功能更强(升序 / 降序混排、动态字段)
劣势
性能稍低(几乎每次比较要多次调用 lessFunc)
Less 逻辑复杂
不如 sort.Slice 语法简洁
如果数据量大(>百万级)且需要高性能,推荐用 sort.Slice 并将多字段比较逻辑手写到一个 Less 中。
3 完整示例
type Change struct {
user string
language string
lines int
}
type lessFunc func(p1, p2 *Change) bool
multiSorter实现了Sort接口,用于对内部变化进行排序
type multiSorter struct {
changes []Change
less []lessFunc
}
Sort 根据传递给 OrderedBy 的 less 函数对参数切片进行排序
func (ms *multiSorter) Sort(changes []Change) {
ms.changes = changes
sort.Sort(ms)
}
OrderedBy 返回一个 Sorter,它使用 less 函数按顺序进行排序。 调用其Sort方法对数据进行排序。
func OrderedBy(less ...lessFunc) *multiSorter {
return &multiSorter{
less: less,
}
}
Len是sort.Interface的一部分
func (ms *multiSorter) Len() int {
return len(ms.changes)
}
Swap是sort.Interface的一部分
func (ms *multiSorter) Swap(i, j int) {
ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i]
}
Less是sort.interface排序的一部分。它是通过循环实现的在找到区分以下内容的比较之前,函数会减少这两个项目(一个比另一个少)。
请注意,它可以调用每次调用两次的功能更少。我们可以更改函数以返回-1、0、1,减少呼叫次数以提高效率。
func (ms *multiSorter) Less(i, j int) bool {
p, q := &ms.changes[i], &ms.changes[j]
// Try all but the last comparison.
var k int
for k = 0; k < len(ms.less)-1; k++ {
less := ms.less[k]
switch {
case less(p, q):
// p < q, so we have a decision.
return true
case less(q, p):
// p > q, so we have a decision.
return false
}
// p == q; try the next comparison.
}
// All comparisons to here said "equal", so just return whatever
// the final comparison reports.
return ms.less[k](p, q)
}
var changes = []Change{
{"gri", "Go", 100},
{"ken", "C", 150},
{"glenda", "Go", 200},
{"rsc", "Go", 200},
{"r", "Go", 100},
{"ken", "Go", 200},
{"dmr", "C", 100},
{"r", "C", 150},
{"gri", "Smalltalk", 80},
}
演示了一种使用不同方法对结构类型进行排序的技术比较中的多个字段集。
我们将“Less”的功能链接在一起,每个示例比较单一功能。
func main() {
// Closures that order the Change structure.
user := func(c1, c2 *Change) bool {
return c1.user < c2.user
}
language := func(c1, c2 *Change) bool {
return c1.language < c2.language
}
increasingLines := func(c1, c2 *Change) bool {
return c1.lines < c2.lines
}
decreasingLines := func(c1, c2 *Change) bool {
return c1.lines > c2.lines // Note: > orders downwards.
}
// Simple use: Sort by user.
OrderedBy(user).Sort(changes)
fmt.Println("By user:", changes)
// More examples.
OrderedBy(user, increasingLines).Sort(changes)
fmt.Println("By user,<lines:", changes)
OrderedBy(user, decreasingLines).Sort(changes)
fmt.Println("By user,>lines:", changes)
OrderedBy(language, increasingLines).Sort(changes)
fmt.Println("By language,<lines:", changes)
OrderedBy(language, increasingLines, user).Sort(changes)
fmt.Println("By language,<lines,user:", changes)
}
4 小结
-
Go sort 包支持的排序方法
sort.Ints, sort.Float64s, sort.Strings sort.Slice, sort.SliceStable
自定义 sort.Interface
-
针对不同类型的排序
int:sort.Ints float:sort.Float64s string:按 Unicode 排序(中文按编码排,不是拼音)
中文拼音排序:需第三方包
例如:github.com/mozillazg/go-pinyin
- 多字段排序 的优势
支持 多字段排序
允许 动态组合多个 less 函数(类似 SQL ORDER BY)
可实现 每字段独立升降序
代码复用性强
对复杂排序场景,是比内置 sort 更强大的设计。
- 点赞
- 收藏
- 关注作者
评论(0)