实现结构体切片的多字段排序

举报
码乐 发表于 2025/11/20 08:51:40 2025/11/20
【摘要】 1 简介本文给出 multiSorter 多字段排序 实现示例,该代码实现了一个可组合多字段排序器,非常类似 SQL ORDER BY: ORDER BY user ASC, lines DESC, ...它允许:动态传入多个比较函数(lessFunc)按优先级链式比较(逐字段比)自定义每个字段升序或降序 2 核心设计思想支持多字段排序(Primary key、Secondary ke...

1 简介

本文给出 multiSorter 多字段排序 实现示例,该代码实现了一个可组合多字段排序器,非常类似 SQL ORDER BY:

		ORDER BY user ASC, lines DESC, ...


它允许:
动态传入多个比较函数(lessFunc)

按优先级链式比较(逐字段比)

自定义每个字段升序或降序

2 核心设计思想

    1. 支持多字段排序(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 无法直接实现多字段排序。

    1. 每个字段可以有独立的排序规则

比如:

  increasingLines := func(c1,c2) { return c1.lines < c2.lines }
  decreasingLines := func(c1,c2) { return c1.lines > c2.lines }

这是 Go 内置 sort 无法直接做到的(尤其是降序排序)。

    1. 排序逻辑可动态组合

例如:

		OrderedBy(language, increasingLines, user).Sort(changes)

这类似 SQL:

		ORDER BY language ASC, lines ASC, user ASC

相比之下,内置 sort.Slice 的方式一次只能写一个 Less 函数,不支持动态组合多个规则。

    1. 可重用、可扩展

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 小结

  1. Go sort 包支持的排序方法

     sort.Ints, sort.Float64s, sort.Strings
    
     sort.Slice, sort.SliceStable
    

自定义 sort.Interface

  1. 针对不同类型的排序

     int:sort.Ints
    
     float:sort.Float64s
    
     		string:按 Unicode 排序(中文按编码排,不是拼音)
    

中文拼音排序:需第三方包
例如:github.com/mozillazg/go-pinyin

  1. 多字段排序 的优势

支持 多字段排序

允许 动态组合多个 less 函数(类似 SQL ORDER BY)

可实现 每字段独立升降序

代码复用性强

对复杂排序场景,是比内置 sort 更强大的设计。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。