新聞中心
排序操作和字符串格式化一樣是很多程序經(jīng)常使用的操作。盡管一個最短的快排程序只要 15 行就可以搞定,但是一個健壯的實現(xiàn)需要更多的代碼,并且我們不希望每次我們需要的時候都重寫或者拷貝這些代碼。

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


咨詢
建站咨詢
