Go+自定义排序教程(4.2)

举报
liuzhen007 发表于 2022/02/20 12:50:38 2022/02/20
【摘要】 ​基础概念自定义排序要求待排序的对象实现sort.Interface接口,只要实现一下三个方法即可Len() int           // 需要排序的集合的长度Less(i, j int) bool // 比较i位置和j位置元素顺序Swap(i, j int)      // 替换i位置和j位置元素最简单排序示例就是定义一个struct,分别实现以上三个方法,即可使用sort.Sort排...

基础概念

自定义排序要求待排序的对象实现sort.Interface接口,只要实现一下三个方法即可

Len() int           // 需要排序的集合的长度
Less(i, j int) bool // 比较i位置和j位置元素顺序
Swap(i, j int)      // 替换i位置和j位置元素

最简单排序示例就是定义一个struct,分别实现以上三个方法,即可使用sort.Sort排序,其中Less函数是定义排序规则的方法,这里可以决定两个比较元素的顺序,返回true代表不用交换,返回false则需要交换位置。

import (
  "fmt"
  "sort"
)

// Student 学生struct
type Student struct {
  Name         string
  Age          int
  EnglishScore int
  MathScore    int
}

// OrderByAge Student数组的别名,用于实现sort.Interface接口
// OrderByAge 可以直接使用sort.Sort来排序
type OrderByAge []Student
func (a OrderByAge) Len() int           { return len(a) }
func (a OrderByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a OrderByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

students := []Student{
  { Name: "Lilei", Age: 18, EnglishScore: 78, MathScore: 79 },
  { Name: "Hanmeimei", Age: 17, EnglishScore: 79, MathScore: 99 },
  { Name: "Hetingting", Age: 19, EnglishScore: 71, MathScore: 68 },
}

println "students:", students

// 对students进行Age字段排序
sort.Sort(OrderByAge(students))
println "by age  :", students

程序会有以下输出:

students: [{Lilei 18 78 79} {Hanmeimei 17 79 99} {Hetingting 19 71 68}]
by age  : [{Hanmeimei 17 79 99} {Lilei 18 78 79} {Hetingting 19 71 68}]

自定义排序

如果想灵活的排序字段可以使用排序函数的别名,然后声明多种不同规则的排序函数,首先有两个主要struct,一个是OrderBy,它是排序函数的别名,用于转换不同类型的排序,另一个是排序器StudentSorter,它是内部有待排序数据和对应的排序规则函数OrderBy,同时StudentSorter实现了sort.Interface接口,可以使用sort.Sort排序。

import (
  "fmt"
  "sort"
)

type Student struct {
  Name         string
  Age          int
  EnglishScore int
  MathScore    int
}

// OrderBy 定义排序函数别名
// 用于定义StudentSorter排序器的排序规则
type OrderBy func(s1, s2 *Student) bool
func (by OrderBy) Sort(students []Student) {
  ss := &StudentSorter{
    Students: students,
    OrderBy:  by, // 它的Sort函数定义的排序的具体规则,它是可变的,所以可以有不同排序规则传递
  }
  sort.Sort(ss)
}

// StudentSorter student的排序器,实现了sort.Interface接口
// 包括student集合数据和排序的规则定义
type StudentSorter struct {
  Students []Student
  OrderBy  OrderBy
}
func (s StudentSorter) Len() int           { return len(s.Students) }
func (s StudentSorter) Swap(i, j int)      { s.Students[i], s.Students[j] = s.Students[j], s.Students[i] }
func (s StudentSorter) Less(i, j int) bool { return s.OrderBy(&s.Students[i], &s.Students[j]) }

students := []Student{
  { Name: "Lilei", Age: 18, EnglishScore: 78, MathScore: 79 },
  { Name: "Hanmeimei", Age: 17, EnglishScore: 79, MathScore: 66 },
  { Name: "Hetingting", Age: 19, EnglishScore: 71, MathScore: 68 },
}

println "students:", students

// 定义按照EnglishScore排序的规则
englishScore := func(s1, s2 *Student) bool {
  return s1.EnglishScore < s2.EnglishScore
}
OrderBy(englishScore).Sort(students)
println "By EnglishScore  :", students

// 定义按照MathScore排序的规则
mathScore := func(s1, s2 *Student) bool {
  return s1.MathScore < s2.MathScore
}
OrderBy(mathScore).Sort(students)
println "By MathScore  :", students

程序会有以下输出:

students: [{Lilei 18 78 79} {Hanmeimei 17 79 66} {Hetingting 19 71 68}]
By EnglishScore  : [{Hetingting 19 71 68} {Lilei 18 78 79} {Hanmeimei 17 79 66}]
By MathScore  : [{Hanmeimei 17 79 66} {Hetingting 19 71 68} {Lilei 18 78 79}]

如果想多个字段按照优先级排序呢,比如先按照EnglishScore排序,如果相等则接着按照MathScore排序呢?我们可以重新设计排序器,其中排序器同样带有带排序数据,不相同的是排序规则为多个,执行排序时候会遍历这些排序规则,如果第一个排序器可以决定顺序就返回,否则就计算下一个排序规则,知道排出顺序。

import (
    "fmt"
    "sort"
)

type Student struct {
    Name         string
    Age          int
    EnglishScore int
    MathScore    int
}

// OrderBy 定义排序函数别名
// 用于定义StudentSorter排序器的排序规则
type OrderBy func(s1, s2 *Student) bool

// MultiSorter student的排序器,实现了sort.Interface接口
// 包括student集合数据和排序的规则定义,其中排序规则是多个带有优先级的排序
type MultiSorter struct {
    Students []Student
    OrderBys []OrderBy
}

func (s *MultiSorter) Sort(students []Student) {
    s.Students = students
    sort.Sort(s)
}

func (s *MultiSorter) Len() int { return len(s.Students) }

func (s *MultiSorter) Swap(i, j int) { s.Students[i], s.Students[j] = s.Students[j], s.Students[i] }

func (s *MultiSorter) Less(i, j int) bool {
    p, q := &s.Students[i], &s.Students[j]
    for index, orderBy := range s.OrderBys {
        // 因为这里需要考虑相等情况下,进行下一轮比较,所以不能直接返回比较结果
        switch {
        case orderBy(p, q):
            // 明确不需要调换位置
            return true
        case orderBy(q, p):
            // 明确需要调换位置,也要返回
            return false
        }
        if index == len(s.Students)-2 {
            return orderBy(p, q) // 之前都没出结果,不论如何最后一个返回结果就行了
        }
    }
    return true
}

// OrderedBy multiSorter的排序器构造函数
// 使用多个排序规则初始化,返回一个排序器
func OrderedBy(orderBys ...OrderBy) *MultiSorter {
    return &MultiSorter{
        OrderBys: orderBys,
    }
}

students := []Student{
    {Name: "Lilei", Age: 18, EnglishScore: 78, MathScore: 79},
    {Name: "Hanmeimei", Age: 17, EnglishScore: 78, MathScore: 66},
    {Name: "Hetingting", Age: 19, EnglishScore: 71, MathScore: 68},
}

fmt.Println("students:", students)

// 定义按照EnglishScore排序的规则
englishScore := func(s1, s2 *Student) bool {
    return s1.EnglishScore < s2.EnglishScore
}

// 定义按照MathScore排序的规则
mathScore := func(s1, s2 *Student) bool {
    return s1.MathScore < s2.MathScore
}

OrderedBy(englishScore).Sort(students)
fmt.Println("By EnglishScore:", students)

OrderedBy(englishScore, mathScore).Sort(students)
fmt.Println("By EnglishScore and MathScore: ", students)

程序会有以下输出:

students: [{Lilei 18 78 79} {Hanmeimei 17 78 66} {Hetingting 19 71 68}]
By EnglishScore: [{Hetingting 19 71 68} {Hanmeimei 17 78 66} {Lilei 18 78 79}]
By EnglishScore and MathScore:  [{Hetingting 19 71 68} {Hanmeimei 17 78 66} {Lilei 18 78 79}]

可以看到当EnglishScore都是78的时候使用了MathScore作为排序的结果。

因为本质上,排序是给出一个固定的规则下,有一个固定的顺序,所以我们可以做一些有意思的排序,比如奇数在前,偶数在后,这个例子和第一个例子只有Less函数的实现不同,其它都是相同的,Less函数逻辑也很简单,如果都是奇数或者都是偶数返回true,如果i是奇数,返回true,否则返回false。

import (
  "fmt"
  "sort"
)

// Student 学生struct
type Student struct {
  Name         string
  Age          int
  EnglishScore int
  MathScore    int
}

// OrderByAge Student数组的别名,用于实现sort.Interface接口
// OrderByAge 可以直接使用sort.Sort来排序
type OrderByAge []Student
func (a OrderByAge) Len() int           { return len(a) }
func (a OrderByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a OrderByAge) Less(i, j int) bool {
  // 请注意,和第一个例子,只有这里不同
  // 奇数在前,偶数在后
  if (a[i].Age + a[j].Age) % 2 == 0 {
    // 说明要么都是奇数,要么都是偶数,返回true
    return true
  }
  
  if a[i].Age % 2 == 0 {
    // 说明i是偶数,j是奇数,需要调换位置
    return false
  }
  // 否则就是不用调换
  return true
}

students := []Student{
  { Name: "Lilei", Age: 18, EnglishScore: 78, MathScore: 79 },
  { Name: "Hanmeimei", Age: 17, EnglishScore: 79, MathScore: 99 },
  { Name: "Hetingting", Age: 19, EnglishScore: 71, MathScore: 68 },
}

println "students:", students

// 对students进行Age字段排序
sort.Sort(OrderByAge(students))
println "by age  :", students

程序会有以下输出:

students: [{Lilei 18 78 79} {Hanmeimei 17 79 99} {Hetingting 19 71 68}]
by age  : [{Hetingting 19 71 68} {Hanmeimei 17 79 99} {Lilei 18 78 79}]

可以看到顺序已经变了,并且是奇数在前偶数在后。

在做一个有意思的排序,我们假设考试分数百分制,忽略具体分数,而是按照等级排序,等级A:90-100 B:80-90 C:70-80 D: 60-70 E:小于60,同级排名不分先后,这里和第一个例子也是只修改了Less的实现,因为它是管排序规则的,或者对应级别后,因为字符串可以直接比较大小,所以直接返回对比大小。

程序会有以下输出:

students: [{Hanmeimei 17 79 99} {Lilei 18 78 79} {Hetingting 19 66 68}]
by EnglishScore: [{Hetingting 19 66 68} {Hanmeimei 17 79 99} {Lilei 18 78 79}]

可以看到66排序在前,79和78顺序没有动,因为它俩级别都是C。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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