mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 14:28:58 +00:00
ch7-6: fmt code
This commit is contained in:
parent
ec31016c6d
commit
d7c078a7f2
114
ch7/ch7-06.md
114
ch7/ch7-06.md
@ -4,23 +4,28 @@
|
|||||||
幸運的是,sort包內置的提供了根據一些排序函數來對任何序列排序的功能。它的設計非常獨到。在很多語言中,排序算法都是和序列數據類型關聯,同時排序函數和具體類型元素關聯。相比之下,Go語言的sort.Sort函數不會對具體的序列和它的元素做任何假設。相反,它使用了一個接口類型sort.Interface來指定通用的排序算法和可能被排序到的序列類型之間的約定。這個接口的實現由序列的具體表示和它希望排序的元素決定,序列的表示經常是一個切片。
|
幸運的是,sort包內置的提供了根據一些排序函數來對任何序列排序的功能。它的設計非常獨到。在很多語言中,排序算法都是和序列數據類型關聯,同時排序函數和具體類型元素關聯。相比之下,Go語言的sort.Sort函數不會對具體的序列和它的元素做任何假設。相反,它使用了一個接口類型sort.Interface來指定通用的排序算法和可能被排序到的序列類型之間的約定。這個接口的實現由序列的具體表示和它希望排序的元素決定,序列的表示經常是一個切片。
|
||||||
|
|
||||||
一個內置的排序算法需要知道三個東西:序列的長度,表示兩個元素比較的結果,一種交換兩個元素的方式;這就是sort.Interface的三個方法:
|
一個內置的排序算法需要知道三個東西:序列的長度,表示兩個元素比較的結果,一種交換兩個元素的方式;這就是sort.Interface的三個方法:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package sort
|
package sort
|
||||||
|
|
||||||
type Interface interface {
|
type Interface interface {
|
||||||
Len() int
|
Len() int
|
||||||
Less(i, j int) bool // i, j are indices of sequence elements
|
Less(i, j int) bool // i, j are indices of sequence elements
|
||||||
Swap(i, j int)
|
Swap(i, j int)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
爲了對序列進行排序,我們需要定義一個實現了這三個方法的類型,然後對這個類型的一個實例應用sort.Sort函數。思考對一個字符串切片進行排序,這可能是最簡單的例子了。下面是這個新的類型StringSlice和它的Len,Less和Swap方法
|
爲了對序列進行排序,我們需要定義一個實現了這三個方法的類型,然後對這個類型的一個實例應用sort.Sort函數。思考對一個字符串切片進行排序,這可能是最簡單的例子了。下面是這個新的類型StringSlice和它的Len,Less和Swap方法
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type StringSlice []string
|
type StringSlice []string
|
||||||
func (p StringSlice) Len() int { return len(p) }
|
func (p StringSlice) Len() int { return len(p) }
|
||||||
func (p StringSlice) Less(i, j int) bool { return p[i] < p[j] }
|
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] }
|
func (p StringSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
```
|
```
|
||||||
|
|
||||||
現在我們可以通過像下面這樣將一個切片轉換爲一個StringSlice類型來進行排序:
|
現在我們可以通過像下面這樣將一個切片轉換爲一個StringSlice類型來進行排序:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sort.Sort(StringSlice(names))
|
sort.Sort(StringSlice(names))
|
||||||
```
|
```
|
||||||
@ -33,56 +38,65 @@ sort.Sort(StringSlice(names))
|
|||||||
我們會運行上面的例子來對一個表格中的音樂播放列表進行排序。每個track都是單獨的一行,每一列都是這個track的屬性像藝術家,標題,和運行時間。想象一個圖形用戶界面來呈現這個表格,併且點擊一個屬性的頂部會使這個列表按照這個屬性進行排序;再一次點擊相同屬性的頂部會進行逆向排序。讓我們看下每個點擊會發生什麽響應。
|
我們會運行上面的例子來對一個表格中的音樂播放列表進行排序。每個track都是單獨的一行,每一列都是這個track的屬性像藝術家,標題,和運行時間。想象一個圖形用戶界面來呈現這個表格,併且點擊一個屬性的頂部會使這個列表按照這個屬性進行排序;再一次點擊相同屬性的頂部會進行逆向排序。讓我們看下每個點擊會發生什麽響應。
|
||||||
|
|
||||||
下面的變量tracks包好了一個播放列表。(One of the authors apologizes for the other author’s musical tastes.)每個元素都不是Track本身而是指向它的指針。盡管我們在下面的代碼中直接存儲Tracks也可以工作,sort函數會交換很多對元素,所以如果每個元素都是指針會更快而不是全部Track類型,指針是一個機器字碼長度而Track類型可能是八個或更多。
|
下面的變量tracks包好了一個播放列表。(One of the authors apologizes for the other author’s musical tastes.)每個元素都不是Track本身而是指向它的指針。盡管我們在下面的代碼中直接存儲Tracks也可以工作,sort函數會交換很多對元素,所以如果每個元素都是指針會更快而不是全部Track類型,指針是一個機器字碼長度而Track類型可能是八個或更多。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
//gopl.io/ch7/sorting
|
//gopl.io/ch7/sorting
|
||||||
type Track struct {
|
type Track struct {
|
||||||
Title string
|
Title string
|
||||||
Artist string
|
Artist string
|
||||||
Album string
|
Album string
|
||||||
Year int
|
Year int
|
||||||
Length time.Duration
|
Length time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var tracks = []*Track{
|
var tracks = []*Track{
|
||||||
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
|
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
|
||||||
{"Go", "Moby", "Moby", 1992, length("3m37s")},
|
{"Go", "Moby", "Moby", 1992, length("3m37s")},
|
||||||
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
|
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
|
||||||
{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
|
{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
|
||||||
}
|
}
|
||||||
|
|
||||||
func length(s string) time.Duration {
|
func length(s string) time.Duration {
|
||||||
d, err := time.ParseDuration(s)
|
d, err := time.ParseDuration(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(s)
|
panic(s)
|
||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
printTracks函數將播放列表打印成一個表格。一個圖形化的展示可能會更好點,但是這個小程序使用text/tabwriter包來生成一個列是整齊對齊和隔開的表格,像下面展示的這樣。註意到*tabwriter.Writer是滿足io.Writer接口的。它會收集每一片寫向它的數據;它的Flush方法會格式化整個表格併且將它寫向os.Stdout(標準輸出)。
|
printTracks函數將播放列表打印成一個表格。一個圖形化的展示可能會更好點,但是這個小程序使用text/tabwriter包來生成一個列是整齊對齊和隔開的表格,像下面展示的這樣。註意到*tabwriter.Writer是滿足io.Writer接口的。它會收集每一片寫向它的數據;它的Flush方法會格式化整個表格併且將它寫向os.Stdout(標準輸出)。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func printTracks(tracks []*Track) {
|
func printTracks(tracks []*Track) {
|
||||||
const format = "%v\t%v\t%v\t%v\t%v\t\n"
|
const format = "%v\t%v\t%v\t%v\t%v\t\n"
|
||||||
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
|
||||||
fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
|
fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
|
||||||
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
|
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
|
||||||
for _, t := range tracks {
|
for _, t := range tracks {
|
||||||
fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
|
fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
|
||||||
}
|
}
|
||||||
tw.Flush() // calculate column widths and print table
|
tw.Flush() // calculate column widths and print table
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
爲了能按照Artist字段對播放列表進行排序,我們會像對StringSlice那樣定義一個新的帶有必鬚Len,Less和Swap方法的切片類型。
|
爲了能按照Artist字段對播放列表進行排序,我們會像對StringSlice那樣定義一個新的帶有必鬚Len,Less和Swap方法的切片類型。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type byArtist []*Track
|
type byArtist []*Track
|
||||||
func (x byArtist) Len() int { return len(x) }
|
func (x byArtist) Len() int { return len(x) }
|
||||||
func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
|
func (x byArtist) Less(i, j int) bool { return x[i].Artist < x[j].Artist }
|
||||||
func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
func (x byArtist) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
```
|
```
|
||||||
|
|
||||||
爲了調用通用的排序程序,我們必鬚先將tracks轉換爲新的byArtist類型,它定義了具體的排序:
|
爲了調用通用的排序程序,我們必鬚先將tracks轉換爲新的byArtist類型,它定義了具體的排序:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sort.Sort(byArtist(tracks))
|
sort.Sort(byArtist(tracks))
|
||||||
```
|
```
|
||||||
|
|
||||||
在按照artist對這個切片進行排序後,printTrack的輸出如下
|
在按照artist對這個切片進行排序後,printTrack的輸出如下
|
||||||
|
|
||||||
```
|
```
|
||||||
Title Artist Album Year Length
|
Title Artist Album Year Length
|
||||||
----- ------ ----- ---- ------
|
----- ------ ----- ---- ------
|
||||||
@ -91,11 +105,15 @@ Go Delilah From the Roots Up 2012 3m38s
|
|||||||
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
||||||
Go Moby Moby 1992 3m37s
|
Go Moby Moby 1992 3m37s
|
||||||
```
|
```
|
||||||
|
|
||||||
如果用戶第二次請求“按照artist排序”,我們會對tracks進行逆向排序。然而我們不需要定義一個有顛倒Less方法的新類型byReverseArtist,因爲sort包中提供了Reverse函數將排序順序轉換成逆序。
|
如果用戶第二次請求“按照artist排序”,我們會對tracks進行逆向排序。然而我們不需要定義一個有顛倒Less方法的新類型byReverseArtist,因爲sort包中提供了Reverse函數將排序順序轉換成逆序。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sort.Sort(sort.Reverse(byArtist(tracks)))
|
sort.Sort(sort.Reverse(byArtist(tracks)))
|
||||||
```
|
```
|
||||||
|
|
||||||
在按照artist對這個切片進行逆向排序後,printTrack的輸出如下
|
在按照artist對這個切片進行逆向排序後,printTrack的輸出如下
|
||||||
|
|
||||||
```
|
```
|
||||||
Title Artist Album Year Length
|
Title Artist Album Year Length
|
||||||
----- ------ ----- ---- ------
|
----- ------ ----- ---- ------
|
||||||
@ -104,7 +122,9 @@ Ready 2 Go Martin Solveig Smash 2011 4m24s
|
|||||||
Go Delilah From the Roots Up 2012 3m38s
|
Go Delilah From the Roots Up 2012 3m38s
|
||||||
Go Ahead Alicia Keys As I Am 2007 4m36s
|
Go Ahead Alicia Keys As I Am 2007 4m36s
|
||||||
```
|
```
|
||||||
|
|
||||||
sort.Reverse函數值得進行更近一步的學習因爲它使用了(§6.3)章中的組合,這是一個重要的思路。sort包定義了一個不公開的struct類型reverse,它嵌入了一個sort.Interface。reverse的Less方法調用了內嵌的sort.Interface值的Less方法,但是通過交換索引的方式使排序結果變成逆序。
|
sort.Reverse函數值得進行更近一步的學習因爲它使用了(§6.3)章中的組合,這是一個重要的思路。sort包定義了一個不公開的struct類型reverse,它嵌入了一個sort.Interface。reverse的Less方法調用了內嵌的sort.Interface值的Less方法,但是通過交換索引的方式使排序結果變成逆序。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package sort
|
package sort
|
||||||
|
|
||||||
@ -114,16 +134,20 @@ func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
|
|||||||
|
|
||||||
func Reverse(data Interface) Interface { return reverse{data} }
|
func Reverse(data Interface) Interface { return reverse{data} }
|
||||||
```
|
```
|
||||||
|
|
||||||
reverse的另外兩個方法Len和Swap隱式地由原有內嵌的sort.Interface提供。因爲reverse是一個不公開的類型,所以導出函數Reverse函數返迴一個包含原有sort.Interface值的reverse類型實例。
|
reverse的另外兩個方法Len和Swap隱式地由原有內嵌的sort.Interface提供。因爲reverse是一個不公開的類型,所以導出函數Reverse函數返迴一個包含原有sort.Interface值的reverse類型實例。
|
||||||
|
|
||||||
爲了可以按照不同的列進行排序,我們必鬚定義一個新的類型例如byYear:
|
爲了可以按照不同的列進行排序,我們必鬚定義一個新的類型例如byYear:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type byYear []*Track
|
type byYear []*Track
|
||||||
func (x byYear) Len() int { return len(x) }
|
func (x byYear) Len() int { return len(x) }
|
||||||
func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year }
|
func (x byYear) Less(i, j int) bool { return x[i].Year < x[j].Year }
|
||||||
func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
func (x byYear) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||||
```
|
```
|
||||||
|
|
||||||
在使用sort.Sort(byYear(tracks))按照年對tracks進行排序後,printTrack展示了一個按時間先後順序的列表:
|
在使用sort.Sort(byYear(tracks))按照年對tracks進行排序後,printTrack展示了一個按時間先後順序的列表:
|
||||||
|
|
||||||
```
|
```
|
||||||
Title Artist Album Year Length
|
Title Artist Album Year Length
|
||||||
----- ------ ----- ---- ------
|
----- ------ ----- ---- ------
|
||||||
@ -132,33 +156,39 @@ Go Ahead Alicia Keys As I Am 2007 4m36s
|
|||||||
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
||||||
Go Delilah From the Roots Up 2012 3m38s
|
Go Delilah From the Roots Up 2012 3m38s
|
||||||
```
|
```
|
||||||
|
|
||||||
對於我們需要的每個切片元素類型和每個排序函數,我們需要定義一個新的sort.Interface實現。如你所見,Len和Swap方法對於所有的切片類型都有相同的定義。下個例子,具體的類型customSort會將一個切片和函數結合,使我們隻需要寫比較函數就可以定義一個新的排序。順便説下,實現了sort.Interface的具體類型不一定是切片類型;customSort是一個結構體類型。
|
對於我們需要的每個切片元素類型和每個排序函數,我們需要定義一個新的sort.Interface實現。如你所見,Len和Swap方法對於所有的切片類型都有相同的定義。下個例子,具體的類型customSort會將一個切片和函數結合,使我們隻需要寫比較函數就可以定義一個新的排序。順便説下,實現了sort.Interface的具體類型不一定是切片類型;customSort是一個結構體類型。
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type customSort struct {
|
type customSort struct {
|
||||||
t []*Track
|
t []*Track
|
||||||
less func(x, y *Track) bool
|
less func(x, y *Track) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x customSort) Len() int
|
func (x customSort) Len() int
|
||||||
func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) }
|
func (x customSort) Less(i, j int) bool { return x.less(x.t[i], x.t[j]) }
|
||||||
func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] }
|
func (x customSort) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] }
|
||||||
```
|
```
|
||||||
|
|
||||||
讓我們定義一個多層的排序函數,它主要的排序鍵是標題,第二個鍵是年,第三個鍵是運行時間Length。下面是該排序的調用,其中這個排序使用了匿名排序函數:
|
讓我們定義一個多層的排序函數,它主要的排序鍵是標題,第二個鍵是年,第三個鍵是運行時間Length。下面是該排序的調用,其中這個排序使用了匿名排序函數:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
sort.Sort(customSort{tracks, func(x, y *Track) bool {
|
sort.Sort(customSort{tracks, func(x, y *Track) bool {
|
||||||
if x.Title != y.Title {
|
if x.Title != y.Title {
|
||||||
return x.Title < y.Title
|
return x.Title < y.Title
|
||||||
}
|
}
|
||||||
if x.Year != y.Year {
|
if x.Year != y.Year {
|
||||||
return x.Year < y.Year
|
return x.Year < y.Year
|
||||||
}
|
}
|
||||||
if x.Length != y.Length {
|
if x.Length != y.Length {
|
||||||
return x.Length < y.Length
|
return x.Length < y.Length
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}})
|
}})
|
||||||
```
|
```
|
||||||
|
|
||||||
這下面是排序的結果。註意到兩個標題是“Go”的track按照標題排序是相同的順序,但是在按照year排序上更久的那個track優先。
|
這下面是排序的結果。註意到兩個標題是“Go”的track按照標題排序是相同的順序,但是在按照year排序上更久的那個track優先。
|
||||||
|
|
||||||
```
|
```
|
||||||
Title Artist Album Year Length
|
Title Artist Album Year Length
|
||||||
----- ------ ----- ---- ------
|
----- ------ ----- ---- ------
|
||||||
@ -167,7 +197,9 @@ Go Delilah From the Roots Up 2012 3m38s
|
|||||||
Go Ahead Alicia Keys As I Am 2007 4m36s
|
Go Ahead Alicia Keys As I Am 2007 4m36s
|
||||||
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
Ready 2 Go Martin Solveig Smash 2011 4m24s
|
||||||
```
|
```
|
||||||
|
|
||||||
盡管對長度爲n的序列排序需要 O(n log n)次比較操作,檢査一個序列是否已經有序至少需要n−1次比較。sort包中的IsSorted函數幫我們做這樣的檢査。像sort.Sort一樣,它也使用sort.Interface對這個序列和它的排序函數進行抽象,但是它從不會調用Swap方法:這段代碼示范了IntsAreSorted和Ints函數和IntSlice類型的使用:
|
盡管對長度爲n的序列排序需要 O(n log n)次比較操作,檢査一個序列是否已經有序至少需要n−1次比較。sort包中的IsSorted函數幫我們做這樣的檢査。像sort.Sort一樣,它也使用sort.Interface對這個序列和它的排序函數進行抽象,但是它從不會調用Swap方法:這段代碼示范了IntsAreSorted和Ints函數和IntSlice類型的使用:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
values := []int{3, 1, 4, 1}
|
values := []int{3, 1, 4, 1}
|
||||||
fmt.Println(sort.IntsAreSorted(values)) // "false"
|
fmt.Println(sort.IntsAreSorted(values)) // "false"
|
||||||
@ -180,8 +212,8 @@ fmt.Println(sort.IntsAreSorted(values)) // "false"
|
|||||||
```
|
```
|
||||||
爲了使用方便,sort包爲[]int,[]string和[]float64的正常排序提供了特定版本的函數和類型。對於其他類型,例如[]int64或者[]uint,盡管路徑也很簡單,還是依賴我們自己實現。
|
爲了使用方便,sort包爲[]int,[]string和[]float64的正常排序提供了特定版本的函數和類型。對於其他類型,例如[]int64或者[]uint,盡管路徑也很簡單,還是依賴我們自己實現。
|
||||||
|
|
||||||
練習7.8:很多圖形界面提供了一個有狀態的多重排序表格插件:主要的排序鍵是最近一次點擊過列頭的列,第二個排序鍵是第二最近點擊過列頭的列,等等。定義一個sort.Interface的實現用在這樣的表格中。比較這個實現方式和重複使用sort.Stable來排序的方式。
|
**練習 7.8:** 很多圖形界面提供了一個有狀態的多重排序表格插件:主要的排序鍵是最近一次點擊過列頭的列,第二個排序鍵是第二最近點擊過列頭的列,等等。定義一個sort.Interface的實現用在這樣的表格中。比較這個實現方式和重複使用sort.Stable來排序的方式。
|
||||||
|
|
||||||
練習7.9:使用html/template包 (§4.6) 替代printTracks將tracks展示成一個HTML表格。將這個解決方案用在前一個練習中,讓每次點擊一個列的頭部産生一個HTTP請求來排序這個表格。
|
**練習 7.9:** 使用html/template包 (§4.6) 替代printTracks將tracks展示成一個HTML表格。將這個解決方案用在前一個練習中,讓每次點擊一個列的頭部産生一個HTTP請求來排序這個表格。
|
||||||
|
|
||||||
練習7.10:sort.Interface類型也可以適用在其它地方。編寫一個IsPalindrome(s sort.Interface) bool函數表明序列s是否是迴文序列,換句話説反向排序不會改變這個序列。假設如果!s.Less(i, j) && !s.Less(j, i)則索引i和j上的元素相等。
|
**練習 7.10:** sort.Interface類型也可以適用在其它地方。編寫一個IsPalindrome(s sort.Interface) bool函數表明序列s是否是迴文序列,換句話説反向排序不會改變這個序列。假設如果!s.Less(i, j) && !s.Less(j, i)則索引i和j上的元素相等。
|
||||||
|
Loading…
Reference in New Issue
Block a user