深入理解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能有所帮助,如果有疑问大家也可以留言交流,小编会尽快给大家回复的。