sort 套件


Go 提供了 sort 套件來協助排序、搜尋任務,對於 []int[]float64[]string,可以透過 IntsFloat64sStrings 來由小而大排序,可以使用 IntsAreSortedFloat64sAreSortedStringsAreSorted 來看看是否已經排序。

若想在已由小而大排序的 []int[]float64[]string 中進行搜尋,可以使用 SearchIntsSearchFloat64sSearchStrings 函式,搜尋結果將傳回找到搜尋值的索引位置,沒有搜尋到的話,傳回的會是可以安插搜尋值的索引位置。例如:

package main

import (
    "fmt"
    "sort"
)

func main() {
    s := []int{8, 2, 6, 3, 1, 4} 
    sort.Ints(s)
    fmt.Println(sort.IntsAreSorted(s)) // true
    fmt.Println(s)                     // [1 2 3 4 6 8]
    fmt.Println(sort.SearchInts(s, 7)) // 5
}

如果想要由大而小排序呢?可以透過 SliceSliceStable,指定一個 less 函式,該函式接受兩個索引,你要傳回布林值表示 i 處的值順序上是否小於 j

package main

import (
    "fmt"
    "sort"
)

func main() {
    s := []int{8, 2, 6, 3, 1, 4} 
    sort.Slice(s, func(i, j int) bool {
        return s[i] > s[j]
    })
    fmt.Println(s)  // [8 6 4 3 2 1]
}

實際上,SliceSliceStable 可用於任意的結構,例如:

package main

import (
    "fmt"
    "sort"
)

func main() {
    family := []struct {
        Name string
        Age  int
    } {{"Irene", 12}, {"Justin", 45}, {"Monica", 42}}

    // 依年齡由小而大排序
    sort.SliceStable(family, func(i, j int) bool {
        return family[i].Age < family[j].Age
    })

    fmt.Println(family) // [{Irene 12} {Monica 42} {Justin 45}]
}

那麼怎麼搜尋上面的 family 呢?例如,找出年齡 45 歲的資料?這可以用 Search,例如:

package main

import (
    "fmt"
    "sort"
)

func main() {
    family := []struct {
        Name string
        Age  int
    } {{"Irene", 12}, {"Justin", 45}, {"Monica", 42}}

    // 依年齡由小而大排序
    sort.SliceStable(family, func(i, j int) bool {
        return family[i].Age < family[j].Age
    })

    fmt.Println(family) // [{Irene 12} {Monica 42} {Justin 45}]

    idx := sort.Search(len(family), func (i int) bool {
        return family[i].Age == 45
    })
    fmt.Println(idx)
}

Search 會使用二分搜尋,第二個參數指定的函式要傳回布林值,表示是否符合搜尋條件,若找到第一個符合的話傳回索引位置,否則傳回第一個參數指定的值。

Search 說明中,還有個猜數字的有趣範例,由程式猜出你心中想的數字:

func GuessingGame() {
    var s string
    fmt.Printf("Pick an integer from 0 to 100.\n")
    answer := sort.Search(100, func(i int) bool {
        fmt.Printf("Is your number <= %d? ", i)
        fmt.Scanf("%s", &s)
        return s != "" && s[0] == 'y'
    })
    fmt.Printf("Your number is %d.\n", answer)
}

sort 還提供了 SortStable 函式,乍看很奇怪:

func Sort(data Interface)
func Stable(data Interface)

Interface 的定義是:

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

這是給有序、具索引的資料結構實現的行為,任何具有 Interface 行為的資料結構,都可以透過 SortStable 函式排序,sort 套件提供的實作有 IntSliceFloat64SliceStringSlice,以 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] }

因此,若要對整數排序,也可以如下:

package main

import (
    "fmt"
    "sort"
)

func main() {
    s := sort.IntSlice([]int{8, 2, 6, 3, 1, 4})
    sort.Sort(s)
    fmt.Println(s)                     // [1 2 3 4 6 8]
}

實際上,IntsFloat64sStrings 函式,內部也只是轉換為 IntSliceFloat64SliceStringSlice,然後呼叫 Sort 罷了:

func Ints(a []int) { Sort(IntSlice(a)) }
func Float64s(a []float64) { Sort(Float64Slice(a)) }
func Strings(a []string) { Sort(StringSlice(a)) }

對於一個實現了 Interface 的資料結構,除了可以使用 SortStable 函式外,若需要反向排序,可以有個簡單方式,透過 Reverse 來包裹。例如:

package main

import (
    "fmt"
    "sort"
)

func main() {
    s := sort.IntSlice([]int{8, 2, 6, 3, 1, 4})
    sort.Sort(sort.Reverse(s))
    fmt.Println(s)                     // [8 6 4 3 2 1]
}

有趣的是 Reverse 的實作,它不過就是將給原本資料結構 Less 方法的 ij 對調罷了:

type reverse struct {
    Interface
}

func (r reverse) Less(i, j int) bool {
    return r.Interface.Less(j, i)
}

func Reverse(data Interface) Interface {
    return &reverse{data}
} 

來自己實現一下 Interface,使用家人的年齡來排序:

package main

import (
    "fmt"
    "sort"
)

type Person struct {
    Name string
    Age  int
}

type Family []Person

func (f Family) Len() int {
    return len(f)
}

func (f Family) Less(i, j int) bool {
    return f[i].Age < f[j].Age
}

func (f Family) Swap(i, j int) {
    f[i], f[j] = f[j], f[i]
}

func main() {
    family := Family{{"Irene", 12}, {"Justin", 45}, {"Monica", 42}}

    sort.Sort(family)
    fmt.Println(family)  // [{Irene 12} {Monica 42} {Justin 45}]

    sort.Sort(sort.Reverse(family))
    fmt.Println(family)  // [{Justin 45} {Monica 42} {Irene 12}]
}