Go从入门到精通——接口(interface)——示例:使用接口进行数据的排序
示例:使用接口进行数据的排序
排序是常见的算法之一,也是常见的面试题之一,程序员对各种排序算法也是津津乐道。
实际使用中,语言的类库会为我们提供健壮、高性能的排序算法库,开发者在了解排序算法基本原理的基础上,应该避免 “造轮子”,直接使用已有的排序算法库,以缩短开发周期,提高开发效率。
Go 语言中在排序时,需要使用者通过 sort.Interface 接口提供数据的一些特性和操作方法。接口定义代码如下:
type Interface interface{
//获取元素数量
Len() int
//小于比较
Less(i,j int) bool
//交换元素
Swap(i,j int) bool
}
一、使用 sort.Interface 接口进行排序
对一系列字符串进行排序时,使用字符串切片([]string)承载多个字符串。使用 type 关键字,将字符串切片([]string)定义为自定义类型 MyStringList。为了让 sort 包能识别 MyStringList,能够对 MyStringList 进行排序,就必须让 MyStringList 实现 sort.Interface 接口。
package main
import (
"fmt"
"sort"
)
//将 []string定义为 MyStringList 类型
type MyStringList []string
//实现 sort.Interface 接口的获取元素数量方法
func (m MyStringList) Len() int { return len(m) }
//实现 sort.Interface 接口的比较元素方法
func (m MyStringList) Less(i, j int) bool { return m[i] < m[j] }
//实现 sort.Interface 接口的交换元素方法
func (m MyStringList) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func main() {
//准备一个内容被打乱顺序的字符串切片
names := MyStringList{
"3.Triple Kill",
"5.Penta Kill",
"2.Double Kill",
"4.Quadra Kill",
"1.FirstBlood",
}
//使用 sort 包进行排序
sort.Sort(names)
//遍历打印结果
for _, v := range names {
fmt.Printf("%s\n", v)
}
}
代码结果运行输出如下:
GOROOT=C:\Program Files\Go #gosetup
GOPATH=D:\go-testfiles #gosetup
"C:\Program Files\Go\bin\go.exe" build -o C:\Users\zuoyang\AppData\Local\Temp\GoLand\___go_build___sort_Interface_go.exe D:\go-testfiles\接口-示例:使用接口进行数据的排序-使用sort.Interface接口进行排序.go #gosetup
C:\Users\zuoyang\AppData\Local\Temp\GoLand\___go_build___sort_Interface_go.exe
1.FirstBlood
2.Double Kill
3.Triple Kill
4.Quadra Kill
5.Penta Kill
进程 已完成,退出代码为 0
二、常见类型的便捷排序
通过实现 sort.Interface 接口的排序过程具有很强的可定制型,可以根据被排序对象比较复杂的特性进行定制。例如,需要多种排序逻辑的需求就适合使用 sort.Interace 接口进行排序。但大部分情况中,只需要对字符串、整型等进行快速排序。Go 语言中提供了一些固定模式的封装以方便开发者迅速对内容进行排序。
1、字符串切片的便捷排序
sort 包中有一个 StringSlice 类型,定义如下:
type StringSlice []string
func (p StringSlice) Len() int { return len(p) }
func (p StringSlice) Less(i, j int) bool { return p[i]<p[j]}
func (p StringSlice) Swap(i, j int) { p[i],p[j] = p[j],p[i]}
func (p StringSlice) Sort() {Sort(p)}
sort 包中的 StringSlice 的代码与 MyStringList 的实现代码几乎一样。因此,只需要使用 sort 包的 StringSlice 就可以更简单快速地进行字符串排序。 《使用 sort.Interface 接口进行排序》排序代码简化后如下:
names := sort.StringSlice{ "3.Triple Kill", "5.Penta Kill", "2.Double Kill", "4.Quadra Kill", "1.FirstBlood", }
sort.Sort(names)
简化后,只要两句代码就可以实现了字符串排序的功能。
2、对整型切片进行排序
除了字符串可以使用 sort 包进行便捷排序外,还可以使用 sort.IntSlice 进行整型切片的排序。sort.IntSlice 的定义如下:
type IntSlice []int
func ( IntSlice) Len() int { return len(p)}
func (p IntSlice) Less(i, j int) bool { return p[i] < p[j]}
func (p IntSlice) Swap(i, j int) { p[i],p[j] = p[j],p[i]}
3、sort 包内建的类型排序接口一览
Go 语言中的 sort 包中定义了一些常见的排序方法,如表下所示:
类型 | 实现 sort.Interface 的类型 | 直接排序方法 | 说明 |
字符串(String) | StringSlice | sort.Strings(a []string) | 字符 ASCII 值升序 |
整型(int) | IntSlice | sort.Ints(a []int) | 数值升序 |
双精度浮点(float64) | Float64Slice | sort.Float64s(a []float64) | 数值升序 |
编程中经常使用的 int32、int64、float32、bool 类型并没有由 sort 包实现,使用时依然需要开发者自己编写。
三、对结构体数据进行排序
除了基本类型的排序,也可以对结构体进行排序。结构体比基本类型更为复杂,排序时不能像数值和字符串一样拥有一些固定的单一原则。
结构体的多个字段在排序中可能会存在多种排序的规则。例如,结构体中的名字按字母升序排列,数值按从小到大的顺序排序。
一般在多种规则同时存在时,需要确定规则的优先度,如先按名字排序,再按年龄排序等。
1、完整实现 sort.Interface 进行结构体排序
将一批英雄名单使用结构体定义,英雄名单的结构体中定义了英雄的名字和分类。排序时要求按照英雄的分类进行排序,相同分类的情况下按名字进行排序,详细代码实现过程如下:
package main
import (
"fmt"
"sort"
)
//声明英雄的分类
type HeroKind int
//定义 HeroKind 常量,类似于枚举
const (
None HeroKind = iota
Tank
Assassin
Mage
)
//定义英雄名单的结构
type Hero struct {
Name string //英雄的名字
Kind HeroKind //英雄的种类
}
//将英雄指针的切片定义为 Heros 类型
type Heros []*Hero
//实现 sort.Interface 接口获取元素数量方法
func (s Heros) Len() int { return len(s) }
//实现 sort.Interface 接口比较元素的方法
func (s Heros) Less(i, j int) bool {
//如果英雄的分类不一致时,优先对分类进行排序
if s[i].Kind != s[j].Kind {
return s[i].Kind < s[j].Kind
}
//默认按英雄名字字符串升序排序
return s[i].Name < s[j].Name
}
//实现 sort.Interface 接口交换元素方法
func (s Heros) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() {
//准备英雄列表
heros := Heros{
&Hero{"海军上将", Tank},
&Hero{"兽王", Tank},
&Hero{"精灵守卫", Tank},
&Hero{"妲己", Mage},
&Hero{"诸葛亮", Mage},
&Hero{"李白", Assassin},
}
//使用 sort 包进行排序
sort.Sort(heros)
//遍历英雄列表打印程序结构
for _, v := range heros {
fmt.Printf("%v\n", v)
}
}
2、使用 sort.Slice 进行切片元素排序
从 Go 1.8 开始,Go 语言在 sort 包中提供了 sort.Slice() 函数进行更为简单的排序方法。
sort.Slice() 函数只要求传入需要排序的数据,以及一个排序时对元素的回调函数,类型为 func(i,j int) bool, sort.Slice() 函数的定义如下:
func Slice(slice interface{}, less func(i,j int) bool)
使用 sort.Slice() 函数,对代码重新调整如下:
package main
import (
"fmt"
"sort"
)
//声明英雄的分类
type HeroKind int
const (
None = iota
Tank
Assassin
Mage
)
//定义英雄名单的结构
type Hero struct {
Name string //英雄的名字
Kind HeroKind //英雄的种类
}
func main() {
//准备英雄列表
heros := []*Hero{
{"海军上将", Tank},
{"兽王", Tank},
{"精灵守卫", Tank},
{"妲己", Mage},
{"诸葛亮", Mage},
{"李白", Assassin},
}
sort.Slice(heros, func(i, j int) bool {
if heros[i].Kind != heros[j].Kind {
return heros[i].Kind < heros[j].Kind
}
return heros[i].Name < heros[j].Name
})
//遍历英雄列表打印程序结构
for _, v := range heros {
fmt.Printf("%v\n", v)
}
}
结果输出如下:
GOROOT=C:\Program Files\Go #gosetup
GOPATH=D:\go-testfiles #gosetup
"C:\Program Files\Go\bin\go.exe" build -o C:\Users\zuoyang\AppData\Local\Temp\GoLand\___go_build____sort_Slice_go.exe "D:\go-testfiles\接口-对结构体数据进行排序-使用 sort.Slice进行切片元素排序.go" #gosetup
C:\Users\zuoyang\AppData\Local\Temp\GoLand\___go_build____sort_Slice_go.exe
&{兽王 1}
&{海军上将 1}
&{精灵守卫 1}
&{李白 2}
&{妲己 3}
&{诸葛亮 3}