查看完整代码,点击这里
最近在使用 Go 语言实现一些简单的排序算法时,发现无法实现一个支持多种类型的排序方法,当然实现一个 int
类型的排序算法是简单的。例如下面的选择排序:
func SelectionSort(arr []int, length int) {
for i := 0; i < length; i++ {
minIndex := i
for j := i + 1; j < length; j++ {
if arr[j] < arr[j - 1] {
minIndex = j
}
}
arr[i], arr[minIndex] = arr[minIndex], arr[i]
}
}
但是如何实现一个方法,可以对字母排序、按照结构体内数据排序... 呢?
我首先想到的当然是 interface
类型,将 int
替换成 interface
就可以了吗?实际是不行的,Go 语言对于类型的要求非常严格,因为程序无法判断 interface
具体是什么数据类型,所以它也不知道该怎么比较一个未知的数据类型,这会导致一个运行时错误。
对 interface
的断言也是行不通的,因为我们也不知道会传入什么数据类型,又怎么判断要断言为什么数据类型呢。
然后我就想到了 Go 自带的 sort 排序包,发现自定义数据类型的排序需要实现对应的接口。以下内容与此文章类似,有部分参考的内容。
Interface 接口
既然 Go 不知道怎么排序未知的数据类型,我们自己来定义比较的规则不就可以了吗?Go 的 sort 包正是这样实现的。
// A type, typically a collection, that satisfies sort.Interface can be
// sorted by the routines in this package. The methods require that the
// elements of the collection be enumerated by an integer index.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less reports whether the element with
// index i should sort before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
Less()
解决的就是问题的症结,我们可以自己定义比较的逻辑。
示例
以下内容是展示如何使用这个接口。
自定义排序的结构体
这里定义了一个学生的数据结构,我们将对所有的学生进行排序,学生包含他的姓名以及成绩,排序的规则是按照学习的成绩排序。
type Student struct {
Name string
Score int
}
type Students []Student
实现排序的接口
func (s Students) Len() int {
return len(s)
}
// 在比较的方法中,定义排序的规则
func (s Students) Less(i, j int) bool {
if s[i].Score < s[j].Score {
return true
} else if s[i].Score > s[j].Score {
return false
} else {
return s[i].Name < s[i].Name
}
}
func (s Students) Swap(i, j int) {
temp := s[i]
s[i] = s[j]
s[j] = temp
}
实现排序逻辑
Go 提供了基于快排实现的排序方法,这里为了体验为什么 Go 这么定义 Interface
接口,我使用了选择排序的方法代替 Go 的快排。
func Sort(s sort.Interface) {
length := s.Len()
for i := 0; i < length; i++ {
minIndex := i
for j := i + 1; j < length; j++ {
if s.Less(j, i) {
minIndex = j
}
}
s.Swap(minIndex, i)
}
}
在这个排序中,我使用了接口中定义的三个方法: Len()
,Less()
,Swap()
。最重要的还是 Less()
,没有它程序就不知道如何去比较两个未知元素的大小。
重写输出
为了更好的输出学生的信息,重写学生的字符串输出格式
func (s Student) String() string {
return fmt.Sprintf("Student: %s %v", s.Name, s.Score)
}
测试输出
通过以下程序测试我们的排序算法
func main() {
arr := []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}
SelectionSort(arr, len(arr))
fmt.Println(arr)
students := student.Students{}
students = append(students, student.Student{"D", 90})
students = append(students, student.Student{"C", 100})
students = append(students, student.Student{"B", 95})
students = append(students, student.Student{"A", 95})
Sort(students)
for _, student := range students {
fmt.Println(student)
}
}
以下是输出结果:
[1 2 3 4 5 6 7 8 9 10]
Student: D 90
Student: A 95
Student: B 95
Student: C 100
总结
查看 sort 包的官方文档,可以看到对 Float64
,int
, String
的结构体和接口实现,他们的原理跟上文描述其实都是一样的,只不过是扩展包替我们预先封装了这些常用的数据类型而已。
实际排序时,优先使用官方提供的 Sort
方法,这里只是为了练习才自己实现了一个选择排序。