go语言结构体多字段多因素排序

上篇结构体排序的文档介绍了如果依据多个字段进行单因素排序, 举例来说, 一个学生实例, 可以单独按照姓名排序, 可以单独按照年龄排序, 可以单独按照体重排序. 本篇文章介绍如果进行多因素排序, 也就是按照姓名排序, 当姓名相同时, 按照年龄排序…

以官方文档的demo来做演示

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package main

import (
"fmt"
"sort"
)

// 数据类型
type Change struct {
user string
language string
lines int
}


// sort接口方法之一(Less)
type lessFunc func(p1, p2 *Change) bool

// 数据集类型, 与上一篇排序文章(多字段单独排序)比较, less字段的数据类型不再是 func(p1, p2 *Change) bool
// 而是 []func(p1, p2 *Change) bool 因为在第一个比较的值相等的情况下, 还要比较第二个值, 所以这里需要多个比较函数
type multiSorter struct {
changes []Change
less []lessFunc
}

// Sort 函数有两个作用
// 第一, 将参数(实际的数据集)赋值给ms对象
// 第二, 调用内置sort函数进行排序操作
func (ms *multiSorter) Sort(changes []Change) {
ms.changes = changes
sort.Sort(ms)
}

// OrderedBy 函数的作用是返回一个multiSorter实例, 并将所有的实际排序函数赋值给实例的less字段,
// 上面已经为multiSorter结构体定义了Sort方法, 所以该函数的返回值可以直接调用Sort方法进行排序
// 该函数中, 为multiSorter结构体中的less字段赋值, Sort方法中又将实际数据集传入, 赋值给multiSorter的changes字段
// 一个函数, 一个方法调用过后, multiSorter实例中两个字段就已经全部被正确赋值, 可以调用系统sort函数进行排序
// 该函数也可看作是一个工厂方法, 用来生成less字段已经被赋值的multiSorter实例
func OrderedBy(less ...lessFunc) *multiSorter {
return &multiSorter{
less: less,
}
}

// Len 为sort接口方法之一
func (ms *multiSorter) Len() int {
return len(ms.changes)
}

// Swap 为sort接口方法之一
func (ms *multiSorter) Swap(i, j int) {
ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i]
}

// Less 为sort接口方法之一
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
// 由于可能有多个需要排序的字段, 也就对应了多个less函数, 当第一个字段的值相等时,
// 需要依次尝试比对后续其他字段的值得大小, 所以这里需要获取比较函数的长度, 以便遍历比较
for k = 0; k < len(ms.less)-1; k++ {
// 提取比较函数, 将函数赋值到新的变量中以便调用
less := ms.less[k]
switch {
case less(p, q):
// 如果 p < q, 返回值为true, 不存在两个值相等需要比较后续字段的情况, 所以这里直接返回
// 如果 p > q, 返回值为false, 则调到下一个case中处理
return true
case less(q, p):
// 如果 p > q, 返回值为false, 不存在两个值相等需要比较后续字段的情况, 所以这里直接返回
return false
}
// 如果代码走到这里, 说明ms.less[k]函数比较后 p == q; 重新开始下一次循环, 更换到下一个比较函数处理
continue
}
// 如果代码走到这里, 说明所有的比较函数执行过后, 所有比较的值都相等
// 直接返回最后一次的比较结果数据即可
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},
}


func main() {
// 预定义排序函数: 按照姓名升序排列
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
}

// 按照姓名升序排列
OrderedBy(user).Sort(changes)
fmt.Println("By user:\t\t", changes)

// 按照姓名升序排列, 姓名相同的按行数升序排列
OrderedBy(user, increasingLines).Sort(changes)
fmt.Println("By user,<lines:\t\t", changes)

// 按姓名升序排列, 姓名相同的按行数降序排列
OrderedBy(user, decreasingLines).Sort(changes)
fmt.Println("By user,>lines:\t\t", changes)

// 按语言升序排列, 语言相同按行数升序排列
OrderedBy(language, increasingLines).Sort(changes)
fmt.Println("By language,<lines:\t", changes)

// 按语言升序排列, 语言相同按行数升序排列, 行数也相同的, 按姓名升序排列
OrderedBy(language, increasingLines, user).Sort(changes)
fmt.Println("By language,<lines,user:", changes)

}

运行结果:

1
2
3
4
5
By user:                 [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken C 150} {ken Go 200} {r Go 100} {r C 150} {rsc Go 200}]
By user,<lines: [{dmr C 100} {glenda Go 200} {gri Smalltalk 80} {gri Go 100} {ken C 150} {ken Go 200} {r Go 100} {r C 150} {rsc Go 200}]
By user,>lines: [{dmr C 100} {glenda Go 200} {gri Go 100} {gri Smalltalk 80} {ken Go 200} {ken C 150} {r C 150} {r Go 100} {rsc Go 200}]
By language,<lines: [{dmr C 100} {ken C 150} {r C 150} {r Go 100} {gri Go 100} {ken Go 200} {glenda Go 200} {rsc Go 200} {gri Smalltalk 80}]
By language,<lines,user: [{dmr C 100} {ken C 150} {r C 150} {gri Go 100} {r Go 100} {glenda Go 200} {ken Go 200} {rsc Go 200} {gri Smalltalk 80}]

与上一篇文章(简称A)中<结构体多字段单独排序>的代码区别 (本篇简称B):

A:

1
2
3
4
5
6
7
8
9
10
type By func(i, j student) bool
func (b By) Sort (students []student) {
// 生成数据集
ss := studentSorter{
students: students,
by: b,
}
// 对数据集进行排序
sort.Sort(ss)
}

B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type multiSorter struct {
changes []Change
less []lessFunc
}

// 在数据集类型中编写Sort排序方法
func (ms *multiSorter) Sort(changes []Change) {
ms.changes = changes
// 对数据集进行排序
sort.Sort(ms)
}

func OrderedBy(less ...lessFunc) *multiSorter {
return &multiSorter{
less: less,
}
}

A B两篇中各截取了部分代码, 均涉及两项操作

  • 生成数据集
  • 排序

A篇的操作为: 单独声明一个By数据类型, 为该数据类型编写方法, 方法中的第一步为生成数据集, 第二步为排序操作. 调用时By(name).Sort(students)中的By(name)是类型转换操作, 后续的.Sort()方法是调用By自己的Sort方法得到的排序结果

B篇的操作为: 单独编写一个OrderBy函数, 该OrderBy函数仅用于生成数据集并返回, 需要注意的是, 这里生成的数据集, 仅包含有排序函数, 不包含实际数据. 由于数据集类型本身自带Sort方法, 所以返回的数据可以直接调用Sort方法执行排序操作, 排序之前, 将参数中的数据填充到数据集中, 形成完整的数据集. 调用时OrderedBy(user).Sort(changes)中的OrderedBy(user)与A篇不同, 此处不是类型转换, 而是函数调用, 后续的.Sort()方法是由OrderedBy()函数返回的multiSorter对象, 调用multiSorter中的Sort方法得到的排序结果

A B两篇的排序方法略有差别, 但本质都是一样的, 核心操作都是生成数据集排序, 只是B篇中, Less方法需要根据多因素排序, 略微复杂一些. 上面的例子是官方的demo, 当然我们也可以完全按照A篇的风格改写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 删除multiSorter数据类型中的Sort方法

// 声明OrderedBy数据类型
type OrderedBy []lessFunc
// 与A篇风格保持一致, 在OrderedBy的方法中进行数据集的完整生成和排序操作
func (ob OrderedBy) Sort (changes []Change) {
// 生成完整的数据集
ms := &multiSorter{
changes: changes,
less: ob,
}
// 排序
sort.Sort(ms)
}

这样修改之后, 调用方式也发生了变更

OrderedBy()是一个类型转换的操作, 根据上面的声明, 它只能转换[]lessFunc类型的数据, 所以需要先声明并赋值[]lessFunc{}对象, 然后才能交给OrderedBy()函数进行类型转换. 转换后其自身拥有Sort方法可供调用

1
2
3
4
5
6
   OrderedBy([]lessFunc{user}).Sort(changes)
fmt.Println("By user:\t\t", changes)

// 按照姓名升序排列, 姓名相同的按行数升序排列
OrderedBy([]lessFunc{user, increasingLines}).Sort(changes)
fmt.Println("By user,<lines:\t\t", changes)

以下是A篇风格的完整代码

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package main

import (
"fmt"
"sort"
)

// 数据类型
type Change struct {
user string
language string
lines int
}


// sort接口方法之一(Less)
type lessFunc func(p1, p2 *Change) bool

// 数据集类型, 与上一篇排序文章(多字段单独排序)比较, less字段的数据类型不再是 func(p1, p2 *Change) bool
// 而是 []func(p1, p2 *Change) bool 因为在第一个比较的值相等的情况下, 还要比较第二个值, 所以这里需要多个比较函数
type multiSorter struct {
changes []Change
less []lessFunc
}

type OrderedBy []lessFunc
func (ob OrderedBy) Sort (changes []Change) {
ms := &multiSorter{
changes: changes,
less: ob,
}
sort.Sort(ms)
}


// Len 为sort接口方法之一
func (ms *multiSorter) Len() int {
return len(ms.changes)
}

// Swap 为sort接口方法之一
func (ms *multiSorter) Swap(i, j int) {
ms.changes[i], ms.changes[j] = ms.changes[j], ms.changes[i]
}

// Less 为sort接口方法之一
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
// 由于可能有多个需要排序的字段, 也就对应了多个less函数, 当第一个字段的值相等时,
// 需要依次尝试比对后续其他字段的值得大小, 所以这里需要获取比较函数的长度, 以便遍历比较
for k = 0; k < len(ms.less)-1; k++ {
// 提取比较函数, 将函数赋值到新的变量中以便调用
less := ms.less[k]
switch {
case less(p, q):
// 如果 p < q, 返回值为true, 不存在两个值相等需要比较后续字段的情况, 所以这里直接返回
// 如果 p > q, 返回值为false, 则调到下一个case中处理
return true
case less(q, p):
// 如果 p > q, 返回值为false, 不存在两个值相等需要比较后续字段的情况, 所以这里直接返回
return false
}
// 如果代码走到这里, 说明ms.less[k]函数比较后 p == q; 重新开始下一次循环, 更换到下一个比较函数处理
continue
}
// 如果代码走到这里, 说明所有的比较函数执行过后, 所有比较的值都相等
// 直接返回最后一次的比较结果数据即可
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},
}


func main() {
// 预定义排序函数: 按照姓名升序排列
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
}


// 按照姓名升序排列
OrderedBy([]lessFunc{user}).Sort(changes)
fmt.Println("By user:\t\t", changes)

// 按照姓名升序排列, 姓名相同的按行数升序排列
OrderedBy([]lessFunc{user, increasingLines}).Sort(changes)
fmt.Println("By user,<lines:\t\t", changes)

// 按姓名升序排列, 姓名相同的按行数降序排列
OrderedBy([]lessFunc{user, decreasingLines}).Sort(changes)
fmt.Println("By user,>lines:\t\t", changes)

// 按语言升序排列, 语言相同按行数升序排列
OrderedBy([]lessFunc{language, increasingLines}).Sort(changes)
fmt.Println("By language,<lines:\t", changes)

// 按语言升序排列, 语言相同按行数升序排列, 行数也相同的, 按姓名升序排列
OrderedBy([]lessFunc{language, increasingLines, user}).Sort(changes)
fmt.Println("By language,<lines,user:", changes)

}