package main import ( "fmt" "sort" "strings" ) var original = []string{ "Nonmetals", " Hydrogen", " Carbon", " Nitrogen", " Oxygen", "Inner Transitionals", " Lanthanides", " Europium", " Cerium", " Actinides", " Uranium", " Plutonium", " Curium", "Alkali Metals", " Lithium", " Sodium", " Potassium", } func main() { fmt.Println("| Original | Sorted |") fmt.Println("|-------------------|-------------------|") sorted := SortedIndentedStrings(original) // 最初是 []string for i := range original { // 在全域性變數中設定 fmt.Printf("|%-19s|%-19s|n", original[i], sorted[i]) } } func SortedIndentedStrings(slice []string) []string { entries := populateEntries(slice) return sortedEntries(entries) } func populateEntries(slice []string) Entries { indent, indentSize := computeIndent(slice) entries := make(Entries, 0) for _, item := range slice { i, level := 0, 0 for strings.HasPrefix(item[i:], indent) { i += indentSize level++ } key := strings.ToLower(strings.TrimSpace(item)) addEntry(level, key, item, &entries) } return entries } func computeIndent(slice []string) (string, int) { for _, item := range slice { if len(item) > 0 && (item[0] == ' ' || item[0] == 't') { whitespace := rune(item[0]) for i, char := range item[1:] { if char != whitespace { i++ return strings.Repeat(string(whitespace), i), i } } } } return "", 0 } func addEntry(level int, key, value string, entries *Entries) { if level == 0 { *entries = append(*entries, Entry{key, value, make(Entries, 0)}) } else { addEntry(level-1, key, value, &((*entries)[entries.Len()-1].children)) } } func sortedEntries(entries Entries) []string { var indentedSlice []string sort.Sort(entries) for _, entry := range entries { populateIndentedStrings(entry, &indentedSlice) } return indentedSlice } func populateIndentedStrings(entry Entry, indentedSlice *[]string) { *indentedSlice = append(*indentedSlice, entry.value) sort.Sort(entry.children) for _, child := range entry.children { populateIndentedStrings(child, indentedSlice) } } type Entry struct { key string value string children Entries } type Entries []Entry func (entries Entries) Len() int { return len(entries) } func (entries Entries) Less(i, j int) bool { return entries[i].key < entries[j].key } func (entries Entries) Swap(i, j int) { entries[i], entries[j] = entries[j], entries[i] }注意 SortedIndentedStrings() 函數有一個很重要的前提就是,字串的縮排是通過讀到的空格或縮排的個數來決定的,下面來看一下輸出結果,為了方便對比,這裡將排序前的結果放在左邊,排序後的結果放在右邊。
| Original | Sorted | |-------------------|-------------------| |Nonmetals |Alkali Metals | | Hydrogen | Lithium | | Carbon | Potassium | | Nitrogen | Sodium | | Oxygen |Inner Transitionals| |Inner Transitionals| Actinides | | Lanthanides | Curium | | Europium | Plutonium | | Cerium | Uranium | | Actinides | Lanthanides | | Uranium | Cerium | | Plutonium | Europium | | Curium |Nonmetals | |Alkali Metals | Carbon | | Lithium | Hydrogen | | Sodium | Nitrogen | | Potassium | Oxygen |其中,SortedIndentedStrings() 函數和它的輔助函數使用到了遞回、函數參照以及指向切片的指標等。
type Entry struct { key string value string children Entries } type Entries []Entry func (entries Entries) Len() int { return len(entries) } func (entries Entries) Less(i, j int) bool { return entries[i].key < entries[j].key } func (entries Entries) Swap(i, j int) { entries[i], entries[j] = entries[j], entries[i] }sort.Interface 介面定義了 3 個方法 Len()、Less() 和 Swap(),它們的函數簽名和 Entries 中的同名方法是一樣的,這就意味著我們可以使用標準庫裡的 sort.Sort() 函數來對一個 Entries 進行排序。
func SortedIndentedStrings(slice []string) []string { entries := populateEntries(slice) return sortedEntries(entries) }匯出的函數 SortedIndentedStrings() 就做了這個工作,雖然我們已經對它進行了重構,讓它把所有東西都傳遞給輔助函數,函數 populateEntries() 傳入一個 []string 並返回一個對應的 Entries([]Entry 型別)。
func populateEntries(slice []string) Entries { indent, indentSize := computeIndent(slice) entries := make(Entries, 0) for _, item := range slice { i, level := 0, 0 for strings.HasPrefix(item[i:], indent) { i += indentSize level++ } key := strings.ToLower(strings.TrimSpace(item)) addEntry(level, key, item, &entries) } return entries }populateEntries() 函數首先以字串的形式得到給定切片裡的一級縮排(如有 4 個空格的字串)和它佔用的位元組數,然後建立一個空的 Entries,並遍歷切片裡的每一個字串,判斷該字串的縮排級別,再建立一個用於排序的鍵。
func computeIndent(slice []string) (string, int) { for _, item := range slice { if len(item) > 0 && (item[0] == ' ' || item[0] == 't') { whitespace := rune(item[0]) for i, char := range item[1:] { if char != whitespace { i++ return strings.Repeat(string(whitespace), i), i } } } } return "", 0 }computeIndent() 函數主要是用來判斷縮排使用的是什麼字元,例如空格或者縮排符等,以及一個縮排級別佔用多少個這樣的字元。
func addEntry(level int, key, value string, entries *Entries) { if level == 0 { *entries = append(*entries, Entry{key, value, make(Entries, 0)}) } else { addEntry(level-1, key, value, &((*entries)[entries.Len()-1].children)) } }addEntry() 是一個遞回函數,它建立一個新的 Entry,如果這個 Entry 的 level 是 0,那就直接增加到 entries 裡去,否則,就將它作為另一個 Entry 的子集。
theEntries := *entries
lastEntry := &theEntries[theEntries.Len()-1]
addEntry(level-1, key, value, &lastEntry.children)
func sortedEntries(entries Entries) []string { var indentedSlice []string sort.Sort(entries) for _, entry := range entries { populateIndentedStrings(entry, &indentedSlice) } return indentedSlice }當呼叫 sortedEntries() 函數的時候,Entries 顯示的結構和原先程式輸出的字串是一樣的,每一個縮排的字串都是上一級縮排的子級,而且還可能有下一級的縮排,依次類推。
func populateIndentedStrings(entry Entry, indentedSlice *[]string) { *indentedSlice = append(*indentedSlice, entry.value) sort.Sort(entry.children) for _, child := range entry.children { populateIndentedStrings(child, indentedSlice) } }populateIndentedStrings() 函數將頂級項追加到建立的切片,然後對頂級項的子項進行排序,並遞回呼叫自身對每一個子項做同樣的處理,這就相當於對每一項的子項以及子項的子項等都做了排序,所以整個字串切片就是已經排好序的了。