深入理解golang的基本类型排序与slice排序
前言
其实golang的排序思路和C和C++有些差别。C默认是对数组进行排序,C++是对一个序列进行排序,Go则更宽泛一些,待排序的可以是任何对象,虽然很多情况下是一个slice(分片,类似于数组),或是包含slice的一个对象。
排序(接口)的三个要素:
1、待排序元素个数n;
2、第i和第j个元素的比较函数cmp;
3、第i和第j个元素的交换swap;
乍一看条件3是多余的,c和c++都不提供swap。c的qsort的用法:qsort(data,n,sizeof(int),cmp_int);data是起始地址,n是元素个数,sizeof(int)是每个元素的大小,cmp_int是一个比较两个int的函数。
c++的sort的用法:sort(data,data+n,cmp_int);data是第一个元素的位置,data+n是最后一个元素的下一个位置,cmp_int是比较函数。
基本类型排序(int、float64和string)
1、升序排序
对于int、float64和string数组或是分片的排序,go分别提供了sort.Ints()、sort.Float64s()和sort.Strings()函数,默认都是从小到大排序。
packagemain
import(
"fmt"
"sort"
)
funcmain(){
intList:=[]int{2,4,3,5,7,6,9,8,1,0}
float8List:=[]float64{4.2,5.9,12.3,10.0,50.4,99.9,31.4,27.81828,3.14}
stringList:=[]string{"a","c","b","d","f","i","z","x","w","y"}
sort.Ints(intList)
sort.Float64s(float8List)
sort.Strings(stringList)
fmt.Printf("%v\n%v\n%v\n",intList,float8List,stringList)
}
2、降序排序
int、float64和string都有默认的升序排序函数,现在问题是如果降序如何?有其他语言编程经验的人都知道,只需要交换cmp的比较法则就可以了,go的实现是类似的,然而又有所不同。go中对某个Type的对象obj排序,可以使用sort.Sort(obj)即可,就是需要对Type类型绑定三个方法:Len()求长度、Less(i,j)比较第i和第j个元素大小的函数、Swap(i,j)交换第i和第j个元素的函数。sort包下的三个类型IntSlice、Float64Slice、StringSlice分别实现了这三个方法,对应排序的是[]int、[]float64和[]string。如果期望逆序排序,只需要将对应的Less函数简单修改一下即可。
go的sort包可以使用sort.Reverse(slice)来调换slice.Interface.Less,也就是比较函数,所以,int、float64和string的逆序排序函数可以这么写:
packagemain
import(
"fmt"
"sort"
)
funcmain(){
intList:=[]int{2,4,3,5,7,6,9,8,1,0}
float8List:=[]float64{4.2,5.9,12.3,10.0,50.4,99.9,31.4,27.81828,3.14}
stringList:=[]string{"a","c","b","d","f","i","z","x","w","y"}
sort.Sort(sort.Reverse(sort.IntSlice(intList)))
sort.Sort(sort.Reverse(sort.Float64Slice(float8List)))
sort.Sort(sort.Reverse(sort.StringSlice(stringList)))
fmt.Printf("%v\n%v\n%v\n",intList,float8List,stringList)
}
3、深入理解排序
sort包中有一个sort.Interface接口,该接口有三个方法Len() 、Less(i,j)和Swap(i,j)。通用排序函数sort.Sort可以排序任何实现了sort.Inferface接口的对象(变量)。对于[]int、[]float64和[]string除了使用特殊指定的函数外,还可以使用改装过的类型IntSclice、Float64Slice和StringSlice,然后直接调用它们对应的Sort()方法;因为这三种类型也实现了sort.Interface接口,所以可以通过sort.Reverse来转换这三种类型的Interface.Less方法来实现逆向排序,这就是前面最后一个排序的使用。
下面使用了一个自定义(用户定义)的Reverse结构体,而不是sort.Reverse函数,来实现逆向排序。
packagemain
import(
"fmt"
"sort"
)
//自定义的Reverse类型
typeReversestruct{
sort.Interface//这样,Reverse可以接纳任何实现了sort.Interface的对象
}
//Reverse只是将其中的Inferface.Less的顺序对调了一下
func(rReverse)Less(i,jint)bool{
returnr.Interface.Less(j,i)
}
funcmain(){
ints:=[]int{5,2,6,3,1,4}
sort.Ints(ints)//特殊排序函数,升序
fmt.Println("aftersortbyInts:\t",ints)
doubles:=[]float64{2.3,3.2,6.7,10.9,5.4,1.8}
sort.Float64s(doubles)
fmt.Println("aftersortbyFloat64s:\t",doubles)//[1.82.33.25.46.710.9]
strings:=[]string{"hello","good","students","morning","people","world"}
sort.Strings(strings)
fmt.Println("aftersortbyStrings:\t",strings)//[goodhellomornigpeoplestudentsworld]
ipos:=sort.SearchInts(ints,-1)//int搜索
fmt.Printf("posof5is%dth\n",ipos)
dpos:=sort.SearchFloat64s(doubles,20.1)//float64搜索
fmt.Printf("posof5.0is%dth\n",dpos)
fmt.Printf("doublesisasc?%v\n",sort.Float64sAreSorted(doubles))
doubles=[]float64{3.5,4.2,8.9,100.98,20.14,79.32}
//sort.Sort(sort.Float64Slice(doubles))//float64排序方法2
//fmt.Println("aftersortbySort:\t",doubles)//[3.54.28.920.1479.32100.98]
(sort.Float64Slice(doubles)).Sort()//float64排序方法3
fmt.Println("aftersortbySort:\t",doubles)//[3.54.28.920.1479.32100.98]
sort.Sort(Reverse{sort.Float64Slice(doubles)})//float64逆序排序
fmt.Println("aftersortbyReversedSort:\t",doubles)//[100.9879.3220.148.94.23.5]
}
sort.Ints/sort.Float64s/sort.Strings分别来对整型/浮点型/字符串型slice进行排序。然后是有个测试是否有序的函数。还有分别对应的search函数,不过,发现搜索函数只能定位到如果存在的话的位置,不存在的话,位置是不对的。
关于一般的数组排序,程序中显示了,有3种方法!目前提供的三种类型int,float64和string呈现对称的,也就是你有的,对应的我也有。关于翻转排序或是逆向排序,就是用个翻转结构体,重写Less()函数即可。
上面的Reverse是个通用的结构体。
上面说了那么多,只是对基本类型进行排序,该到说说struct结构体类型的排序的时候了,实际中这个用得到的会更多。
结构体类型的排序
结构体类型的排序是通过使用sort.Sort(slice)实现的,只要slice实现了sort.Interface的三个方法就可以。虽然这么说,但是排序的方法却有那么好几种。首先一种就是模拟排序[]int构造对应的IntSlice类型,然后对IntSlice类型实现Interface的三个方法。
1、模拟IntSlice排序
packagemain
import(
"fmt"
"sort"
)
typePersonstruct{
Namestring
Ageint
}
//按照Person.Age从大到小排序
typePersonSlice[]Person
func(aPersonSlice)Len()int{//重写Len()方法
returnlen(a)
}
func(aPersonSlice)Swap(i,jint){//重写Swap()方法
a[i],a[j]=a[j],a[i]
}
func(aPersonSlice)Less(i,jint)bool{//重写Less()方法,从大到小排序
returna[j].Age<a[i].Age
}
funcmain(){
people:=[]Person{
{"zhangsan",12},
{"lisi",30},
{"wangwu",52},
{"zhaoliu",26},
}
fmt.Println(people)
sort.Sort(PersonSlice(people))//按照Age的逆序排序
fmt.Println(people)
sort.Sort(sort.Reverse(PersonSlice(people)))//按照Age的升序排序
fmt.Println(people)
}
这完全是一种模拟的方式,所以如果懂了IntSlice自然就理解这里了,反过来,理解了这里那么IntSlice那里也就懂了。
这种方法的缺点是:根据Age排序需要重新定义PersonSlice方法,绑定Len、Less和Swap方法,如果需要根据Name排序,又需要重新写三个函数;如果结构体有4个字段,有四种类型的排序,那么就要写3×4=12个方法,即使有一些完全是多余的,O__O”…仔细思量一下,根据不同的标准Age或是Name,真正不同的体现在Less方法上,所以可以将Less抽象出来,每种排序的Less让其变成动态的,比如下面一种方法。
2、封装成Wrapper
packagemain
import(
"fmt"
"sort"
)
typePersonstruct{
Namestring
Ageint
}
typePersonWrapperstruct{//注意此处
people[]Person
byfunc(p,q*Person)bool
}
func(pwPersonWrapper)Len()int{//重写Len()方法
returnlen(pw.people)
}
func(pwPersonWrapper)Swap(i,jint){//重写Swap()方法
pw.people[i],pw.people[j]=pw.people[j],pw.people[i]
}
func(pwPersonWrapper)Less(i,jint)bool{//重写Less()方法
returnpw.by(&pw.people[i],&pw.people[j])
}
funcmain(){
people:=[]Person{
{"zhangsan",12},
{"lisi",30},
{"wangwu",52},
{"zhaoliu",26},
}
fmt.Println(people)
sort.Sort(PersonWrapper{people,func(p,q*Person)bool{
returnq.Age<p.Age//Age递减排序
}})
fmt.Println(people)
sort.Sort(PersonWrapper{people,func(p,q*Person)bool{
returnp.Name<q.Name//Name递增排序
}})
fmt.Println(people)
}
这种方法将[]Person和比较的准则cmp封装在了一起,形成了PersonWrapper函数,然后在其上绑定Len、Less和Swap方法。实际上sort.Sort(pw)排序的是pw中的people,这就是前面说的,go的排序未必就是针对的一个数组或是slice,而可以是一个对象中的数组或是slice。
3、进一步封装
感觉方法2已经很不错了,唯一一个缺点是,在main中使用的时候暴露了sort.Sort的使用,还有就是PersonWrapper的构造。为了让main中使用起来更为方便,me们可以再简单的封装一下,构造一个SortPerson方法,如下:
packagemain
import(
"fmt"
"sort"
)
typePersonstruct{
Namestring
Ageint
}
typePersonWrapperstruct{
people[]Person
byfunc(p,q*Person)bool
}
typeSortByfunc(p,q*Person)bool
func(pwPersonWrapper)Len()int{//重写Len()方法
returnlen(pw.people)
}
func(pwPersonWrapper)Swap(i,jint){//重写Swap()方法
pw.people[i],pw.people[j]=pw.people[j],pw.people[i]
}
func(pwPersonWrapper)Less(i,jint)bool{//重写Less()方法
returnpw.by(&pw.people[i],&pw.people[j])
}
//封装成SortPerson方法
funcSortPerson(people[]Person,bySortBy){
sort.Sort(PersonWrapper{people,by})
}
funcmain(){
people:=[]Person{
{"zhangsan",12},
{"lisi",30},
{"wangwu",52},
{"zhaoliu",26},
}
fmt.Println(people)
sort.Sort(PersonWrapper{people,func(p,q*Person)bool{
returnq.Age<p.Age//Age递减排序
}})
fmt.Println(people)
SortPerson(people,func(p,q*Person)bool{
returnp.Name<q.Name//Name递增排序
})
fmt.Println(people)
}
在方法2的基础上构造了SortPerson函数,使用的时候传过去一个[]Person和一个cmp函数。
4、另一种思路
packagemain
import(
"fmt"
"sort"
)
typePersonstruct{
Namestring
Weightint
}
typePersonSlice[]Person
func(sPersonSlice)Len()int{returnlen(s)}
func(sPersonSlice)Swap(i,jint){s[i],s[j]=s[j],s[i]}
typeByNamestruct{PersonSlice}//将PersonSlice包装起来到ByName中
func(sByName)Less(i,jint)bool{returns.PersonSlice[i].Name<s.PersonSlice[j].Name}//将Less绑定到ByName上
typeByWeightstruct{PersonSlice}//将PersonSlice包装起来到ByWeight中
func(sByWeight)Less(i,jint)bool{returns.PersonSlice[i].Weight<s.PersonSlice[j].Weight}//将Less绑定到ByWeight上
funcmain(){
s:=[]Person{
{"apple",12},
{"pear",20},
{"banana",50},
{"orange",87},
{"hello",34},
{"world",43},
}
sort.Sort(ByWeight{s})
fmt.Println("Peoplebyweight:")
printPeople(s)
sort.Sort(ByName{s})
fmt.Println("\nPeoplebyname:")
printPeople(s)
}
funcprintPeople(s[]Person){
for_,o:=ranges{
fmt.Printf("%-8s(%v)\n",o.Name,o.Weight)
}
}
对结构体的排序,暂时就到这里。第一种排序对只根据一个字段的比较合适,另外三个是针对可能根据多个字段排序的。方法4我认为每次都要多构造一个ByXXX,颇为不便,这样多麻烦,不如方法2和方法3来的方便,直接传进去一个cmp。方法2、3没有太大的差别,3只是简单封装了一下而已,对于使用者来说,可能会更方便一些,而且也会更少的出错。
总结
以上就是关于golang基本类型排序与slice排序的全部内容,希望这篇文章的内容对啊大家学习或者使用Golang能有所帮助,如果有疑问大家也可以留言交流,小编会尽快给大家回复的。