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

前言

有时候我们需要处理一份数据,需要多个字段作为条件,联合进行排序。

代码

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

运行结果

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}]
posted @ 2022-04-15 22:30  牛奔  阅读(269)  评论(0编辑  收藏  举报