十年網(wǎng)站開(kāi)發(fā)經(jīng)驗(yàn) + 多家企業(yè)客戶(hù) + 靠譜的建站團(tuán)隊(duì)
量身定制 + 運(yùn)營(yíng)維護(hù)+專(zhuān)業(yè)推廣+無(wú)憂(yōu)售后,網(wǎng)站問(wèn)題一站解決
排序操作和字符串格式化一樣是很多程序經(jīng)常使用的操作。盡管一個(gè)最短的快排程序只要 15 行就可以搞定,但是一個(gè)健壯的實(shí)現(xiàn)需要更多的代碼,并且我們不希望每次我們需要的時(shí)候都重寫(xiě)或者拷貝這些代碼。

公司主營(yíng)業(yè)務(wù):網(wǎng)站制作、網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶(hù)真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。創(chuàng)新互聯(lián)建站是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶(hù)帶來(lái)驚喜。創(chuàng)新互聯(lián)建站推出三穗免費(fèi)做網(wǎng)站回饋大家。
幸運(yùn)的是,sort 包內(nèi)置的提供了根據(jù)一些排序函數(shù)來(lái)對(duì)任何序列排序的功能。它的設(shè)計(jì)非常獨(dú)到。在很多語(yǔ)言中,排序算法都是和序列數(shù)據(jù)類(lèi)型關(guān)聯(lián),同時(shí)排序函數(shù)和具體類(lèi)型元素關(guān)聯(lián)。
相比之下,Go語(yǔ)言的 sort.Sort 函數(shù)不會(huì)對(duì)具體的序列和它的元素做任何假設(shè)。相反,它使用了一個(gè)接口類(lèi)型 sort.Interface 來(lái)指定通用的排序算法和可能被排序到的序列類(lèi)型之間的約定。這個(gè)接口的實(shí)現(xiàn)由序列的具體表示和它希望排序的元素決定,序列的表示經(jīng)常是一個(gè)切片。
一個(gè)內(nèi)置的排序算法需要知道三個(gè)東西:序列的長(zhǎng)度,表示兩個(gè)元素比較的結(jié)果,一種交換兩個(gè)元素的方式;這就是 sort.Interface 的三個(gè)方法:
package sort
type Interface interface {
Len() int // 獲取元素?cái)?shù)量
Less(i, j int) bool // i,j是序列元素的指數(shù)。
Swap(i, j int) // 交換元素
}
為了對(duì)序列進(jìn)行排序,我們需要定義一個(gè)實(shí)現(xiàn)了這三個(gè)方法的類(lèi)型,然后對(duì)這個(gè)類(lèi)型的一個(gè)實(shí)例應(yīng)用 sort.Sort 函數(shù)。思考對(duì)一個(gè)字符串切片進(jìn)行排序,這可能是最簡(jiǎn)單的例子了。下面是這個(gè)新的類(lèi)型 MyStringList 和它的 Len,Less 和 Swap 方法
type MyStringList []string
func (p MyStringList ) Len() int { return len(m) }
func (p MyStringList ) Less(i, j int) bool { return m[i] < m[j] }
func (p MyStringList ) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
對(duì)一系列字符串進(jìn)行排序時(shí),使用字符串切片([]string)承載多個(gè)字符串。使用 type 關(guān)鍵字,將字符串切片([]string)定義為自定義類(lèi)型 MyStringList。為了讓 sort 包能識(shí)別 MyStringList,能夠?qū)?MyStringList 進(jìn)行排序,就必須讓 MyStringList 實(shí)現(xiàn) sort.Interface 接口。
下面是對(duì)字符串排序的詳細(xì)代碼(代碼1):
package main
import (
"fmt"
"sort"
)
// 將[]string定義為MyStringList類(lèi)型
type MyStringList []string
// 實(shí)現(xiàn)sort.Interface接口的獲取元素?cái)?shù)量方法
func (m MyStringList) Len() int {
return len(m)
}
// 實(shí)現(xiàn)sort.Interface接口的比較元素方法
func (m MyStringList) Less(i, j int) bool {
return m[i] < m[j]
}
// 實(shí)現(xiàn)sort.Interface接口的交換元素方法
func (m MyStringList) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
func main() {
// 準(zhǔn)備一個(gè)內(nèi)容被打亂順序的字符串切片
names := MyStringList{
"3. Triple Kill",
"5. Penta Kill",
"2. Double Kill",
"4. Quadra Kill",
"1. First Blood",
}
// 使用sort包進(jìn)行排序
sort.Sort(names)
// 遍歷打印結(jié)果
for _, v := range names {
fmt.Printf("%s\n", v)
}
}
代碼輸出結(jié)果:
1. First Blood
2. Double Kill
3. Triple Kill
4. Quadra Kill
5. Penta Kill
代碼說(shuō)明如下:
names := []string {
"3. Triple Kill",
"5. Penta Kill",
"2. Double Kill",
"4. Quadra Kill",
"1. First Blood",
}
通過(guò)實(shí)現(xiàn) sort.Interface 接口的排序過(guò)程具有很強(qiáng)的可定制性,可以根據(jù)被排序?qū)ο蟊容^復(fù)雜的特性進(jìn)行定制。例如,需要多種排序邏輯的需求就適合使用 sort.Interface 接口進(jìn)行排序。但大部分情況中,只需要對(duì)字符串、整型等進(jìn)行快速排序。Go語(yǔ)言中提供了一些固定模式的封裝以方便開(kāi)發(fā)者迅速對(duì)內(nèi)容進(jìn)行排序。
sort 包中有一個(gè) StringSlice 類(lèi)型,定義如下:
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] }
// Sort is a convenience method.
func (p StringSlice) Sort() { Sort(p) }
sort 包中的 StringSlice 的代碼與 MyStringList 的實(shí)現(xiàn)代碼幾乎一樣。因此,只需要使用 sort 包的 StringSlice 就可以更簡(jiǎn)單快速地進(jìn)行字符串排序。將代碼1中的排序代碼簡(jiǎn)化后如下所示:
names := sort.StringSlice{
"3. Triple Kill",
"5. Penta Kill",
"2. Double Kill",
"4. Quadra Kill",
"1. First Blood",
}
sort.Sort(names)
簡(jiǎn)化后,只要兩句代碼就實(shí)現(xiàn)了字符串排序的功能。
除了字符串可以使用 sort 包進(jìn)行便捷排序外,還可以使用 sort.IntSlice 進(jìn)行整型切片的排序。sort.IntSlice 的定義如下:
type IntSlice []int
func (p 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] }
// Sort is a convenience method.
func (p IntSlice) Sort() { Sort(p) }
sort 包在 sort.Interface 對(duì)各類(lèi)型的封裝上還有更進(jìn)一步的簡(jiǎn)化,下面使用 sort.Strings 繼續(xù)對(duì)代碼1進(jìn)行簡(jiǎn)化,代碼如下:
names := []string{
"3. Triple Kill",
"5. Penta Kill",
"2. Double Kill",
"4. Quadra Kill",
"1. First Blood",
}
sort.Strings(names)
// 遍歷打印結(jié)果
for _, v := range names {
fmt.Printf("%s\n", v)
}
代碼說(shuō)明如下:
Go語(yǔ)言中的 sort 包中定義了一些常見(jiàn)類(lèi)型的排序方法,如下表所示。
| 類(lèi) 型 | 實(shí)現(xiàn) sort.lnterface 的類(lèi)型 | 直接排序方法 | 說(shuō) 明 |
|---|---|---|---|
| 字符串(String) | StringSlice | sort.Strings(a [] string) | 字符 ASCII 值升序 |
| 整型(int) | IntSlice | sort.Ints(a []int) | 數(shù)值升序 |
| 雙精度浮點(diǎn)(float64) | Float64Slice | sort.Float64s(a []float64) | 數(shù)值升序 |
編程中經(jīng)常用到的 int32、int64、float32、bool 類(lèi)型并沒(méi)有由 sort 包實(shí)現(xiàn),使用時(shí)依然需要開(kāi)發(fā)者自己編寫(xiě)。
除了基本類(lèi)型的排序,也可以對(duì)結(jié)構(gòu)體進(jìn)行排序。結(jié)構(gòu)體比基本類(lèi)型更為復(fù)雜,排序時(shí)不能像數(shù)值和字符串一樣擁有一些固定的單一原則。結(jié)構(gòu)體的多個(gè)字段在排序中可能會(huì)存在多種排序的規(guī)則,例如,結(jié)構(gòu)體中的名字按字母升序排列,數(shù)值按從小到大的順序排序。一般在多種規(guī)則同時(shí)存在時(shí),需要確定規(guī)則的優(yōu)先度,如先按名字排序,再按年齡排序等。
將一批英雄名單使用結(jié)構(gòu)體定義,英雄名單的結(jié)構(gòu)體中定義了英雄的名字和分類(lèi)。排序時(shí)要求按照英雄的分類(lèi)進(jìn)行排序,相同分類(lèi)的情況下按名字進(jìn)行排序,詳細(xì)代碼實(shí)現(xiàn)過(guò)程如下。
結(jié)構(gòu)體排序代碼(代碼2):
package main
import (
"fmt"
"sort"
)
// 聲明英雄的分類(lèi)
type HeroKind int
// 定義HeroKind常量, 類(lèi)似于枚舉
const (
None HeroKind = iota
Tank
Assassin
Mage
)
// 定義英雄名單的結(jié)構(gòu)
type Hero struct {
Name string // 英雄的名字
Kind HeroKind // 英雄的種類(lèi)
}
// 將英雄指針的切片定義為Heros類(lèi)型
type Heros []*Hero
// 實(shí)現(xiàn)sort.Interface接口取元素?cái)?shù)量方法
func (s Heros) Len() int {
return len(s)
}
// 實(shí)現(xiàn)sort.Interface接口比較元素方法
func (s Heros) Less(i, j int) bool {
// 如果英雄的分類(lèi)不一致時(shí), 優(yōu)先對(duì)分類(lèi)進(jìn)行排序
if s[i].Kind != s[j].Kind {
return s[i].Kind < s[j].Kind
}
// 默認(rèn)按英雄名字字符升序排列
return s[i].Name < s[j].Name
}
// 實(shí)現(xiàn)sort.Interface接口交換元素方法
func (s Heros) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func main() {
// 準(zhǔn)備英雄列表
heros := Heros{
&Hero{"呂布", Tank},
&Hero{"李白", Assassin},
&Hero{"妲己", Mage},
&Hero{"貂蟬", Assassin},
&Hero{"關(guān)羽", Tank},
&Hero{"諸葛亮", Mage},
}
// 使用sort包進(jìn)行排序
sort.Sort(heros)
// 遍歷英雄列表打印排序結(jié)果
for _, v := range heros {
fmt.Printf("%+v\n", v)
}
}
代碼輸出如下:
&{Name:關(guān)羽 Kind:1}
&{Name:呂布 Kind:1}
&{Name:李白 Kind:2}
&{Name:貂蟬 Kind:2}
&{Name:妲己 Kind:3}
&{Name:諸葛亮 Kind:3}
代碼說(shuō)明如下:
從 Go 1.8 開(kāi)始,Go語(yǔ)言在 sort 包中提供了 sort.Slice() 函數(shù)進(jìn)行更為簡(jiǎn)便的排序方法。sort.Slice() 函數(shù)只要求傳入需要排序的數(shù)據(jù),以及一個(gè)排序時(shí)對(duì)元素的回調(diào)函數(shù),類(lèi)型為 func(i,j int)bool,sort.Slice() 函數(shù)的定義如下:
func Slice(slice interface{}, less func(i, j int) bool)
使用 sort.Slice() 函數(shù),對(duì)代碼2重新優(yōu)化的完整代碼如下:
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},
{"李白", Assassin},
{"妲己", Mage},
{"貂蟬", Assassin},
{"關(guān)羽", Tank},
{"諸葛亮", Mage},
}
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)
}
}
第 33 行到第 39 行加粗部分是新添加的 sort.Slice() 及回調(diào)函數(shù)部分。對(duì)比前面的代碼,這里去掉了 Heros 及接口實(shí)現(xiàn)部分的代碼。
使用 sort.Slice() 不僅可以完成結(jié)構(gòu)體切片排序,還可以對(duì)各種切片類(lèi)型進(jìn)行自定義排序。