Golang排序

Go排序教程展示了如何在Golang中对切片和用户定义的集合进行排序。

$ go version
go version go1.18.1 linux/amd64

我们使用Go版本1.18。

排序

排序是以有序的顺序排列元素。在计算机科学中,开发了许多算法来对数据进行排序,包括归并排序、快速排序、选择排序或冒泡排序。(排序的另一个含义是分类,将具有相似属性的元素分组。)

与排序相反,以随机或无意义的顺序重新排列一系列元素,称为洗牌。

基本数据类型可以按自然顺序排序,可以按字母顺序也可以按数字顺序。排序键指定用于执行排序的标准。我们还可以按多个键对对象进行排序。例如,在对用户进行排序时,可以将用户的姓名作为主排序键,将职业作为次排序键。

排序顺序

标准顺序称为升序:a到z,0到9。相反的顺序称为降序:z到a,9到0。对于日期和时间,升序意味着较早的值在较晚的值之前,例如2019年1月1日将比2023年1月1日提前。

稳定排序

在稳定的排序算法中,相等元素的初始顺序被保留。有些排序算法天生稳定,有些则不然。例如,冒泡排序和归并排序是稳定的排序算法。另一方面,堆排序和快速排序是不稳定排序算法的例子。

考虑以下值:3715593。稳定排序产生以下结果:1335579。值3和5的顺序保持不变。不稳定的排序可能会产生以下结果:1335579

Go中的排序

Go具有用于排序的标准sort包。排序函数就地排序数据。对于基本数据类型,我们有内置函数,例如sort.Intssort.Strings。对于更复杂的类型,我们需要通过实现sort.Interface来创建我们自己的排序:

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

该接口包含三个函数:LenLessSwap

去排序整数

sort.Ints按升序对整数切片进行排序。Reverse函数返回数据的倒序。

package main

import (
    "fmt"
    "sort"
)

func main() {

    vals := []int{3, 1, 0, 7, 2, 4, 8, 6}
    sort.Ints(vals)
    fmt.Println(vals)

    sort.Sort(sort.Reverse(sort.IntSlice(vals)))
    fmt.Println(vals)
}

该示例按升序和降序对整数切片进行排序。

$ go run sort_ints.go
[0 1 2 3 4 6 7 8]
[8 7 6 4 3 2 1 0]

去排序字符串

sort.Strings按升序对一段字符串进行排序。

package main

import (
    "fmt"
    "sort"
)

func main() {

    words := []string{"bear", "atom", "world", "falcon", "cloud", "forest"}

    sort.Strings(words)
    fmt.Println(words)

    sort.Sort(sort.Reverse(sort.StringSlice(words)))
    fmt.Println(words)
}

我们有一段话。我们按升序和降序对单词进行排序。

sort.Strings(words)

sort.Strings按升序对单词进行排序。

sort.Sort(sort.Reverse(sort.StringSlice(words)))

借助sort.Reversesort.StringSlice函数,我们按降序对单词进行排序。

$ go run sort_strings.go
[atom bear cloud falcon forest world]
[world forest falcon cloud bear atom]

去排序重音词

在下一个示例中,我们对重音词进行排序。对于此任务,我们使用x/text模块。

package main

import (
    "fmt"

    "golang.org/x/text/collate"
    "golang.org/x/text/language"
)

func main() {

    words := []string{"čaj", "auto", "pot", "márny", "kľak", "chyba", "drevo",
        "cibuľa", "džíp", "džem", "šum", "pól", "čučoriedka", "banán", "čerešňa",
        "červený", "klam", "čierny", "tŕň", "pôst", "hôrny", "mat", "chobot",
        "cesnak", "kĺb", "mäta", "ďateľ", "troska", "sýkorka", "elektrón",
        "fuj", "zem", "guma", "hora", "gejzír", "ihla", "pýr", "hrozno",
        "jazva", "džavot", "lom"}

    c := collate.New(language.Slovak)
    c.SortStrings(words)

    fmt.Println(words)
}

我们有一段斯洛伐克语单词。

c := collate.New(language.Slovak)
c.SortStrings(words)

我们为斯洛伐克语创建了一个排序规则,并使用SortStrings对单词进行排序。

$ go run sort_accented_words.go 
[auto banán cesnak cibuľa čaj čerešňa červený čierny čučoriedka ďateľ drevo
džavot džem džíp elektrón fuj gejzír guma hora hôrny hrozno chobot chyba ihla
jazva kľak klam kĺb lom márny mat mäta pól pot pôst pýr sýkorka šum tŕň troska
zem]

输出不是100%正确;但它是所有编程语言中最接近的解决方案之一。

去检查排序

我们可以使用sort.StringsAreSortedsort.IntsAreSortedsort.SliceIsSorted函数检查数据是否已排序。

package main

import (
    "fmt"
    "sort"
)

func main() {

    words := []string{"bear", "atom", "world", "falcon", "cloud", "forest"}

    sorted := sort.StringsAreSorted(words)
    fmt.Println(sorted)

    sort.Sort(sort.StringSlice(words))

    sorted = sort.StringsAreSorted(words)
    fmt.Println(sorted)

    // --------------------------------------------

    vals := []int{5, 2, 6, 3, 1, 7, 8, 4, 0}

    sorted = sort.IntsAreSorted(vals)
    fmt.Println(sorted)

    sort.Ints(vals)

    sorted = sort.IntsAreSorted(vals)
    fmt.Println(sorted)
}

在代码示例中,我们检查我们的两个切片是否已排序。

$ go run check_sorting.go
false
true
false
true

自定义排序

对于自定义排序,我们需要实现sort.Interface函数。

package main

import (
    "fmt"
    "sort"
)

type ByLength []string

func (s ByLength) Len() int {
    return len(s)
}

func (s ByLength) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

func (s ByLength) Less(i, j int) bool {
    return len(s[i]) < len(s[j])
}

func main() {
    words := []string{"cloud", "atom", "sea", "by", "forest", "maintenance"}
    sort.Sort(ByLength(words))
    fmt.Println(words)
}

在代码示例中,我们按长度对一段单词进行排序。

type ByLength []string

我们创建了一个自定义类型ByLength,它是内置[]string的别名。

func (s ByLength) Len() int {
    return len(s)
}

func (s ByLength) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}

func (s ByLength) Less(i, j int) bool {
    return len(s[i]) < len(s[j])
}

我们实现排序接口需要的三个函数。

sort.Sort(ByLength(words))

我们将字符串单词的切片转换为我们的类型,然后对该类型切片调用Sort函数。

$ go run custom_sorting.go
[by sea atom cloud forest maintenance]

单词按长度升序排列。

去sort.Slice

sort.Slice是一个方便的函数,可以在切片中对数据进行排序,而无需创建自定义类型。

package main

import (
    "fmt"
    "sort"
)

func main() {

    words := []string{"cloud", "atom", "sea", "by", "forest", "maintenance"}

    sort.Slice(words, func(i1, i2 int) bool {
        return len(words[i1]) < len(words[i2])
    })

    fmt.Println(words)

    sort.Slice(words, func(i1, i2 int) bool {
        return len(words[i1]) > len(words[i2])
    })

    fmt.Println(words)
}

我们根据单词的长度对单词切片进行排序。我们将匿名比较函数传递给sort.Slice

$ go run slice_fun.go
[by sea atom cloud forest maintenance]
[maintenance forest cloud atom sea by]

按键排序地图

在以下示例中,我们按键对地图进行排序。

package main

import (
    "fmt"
    "sort"
)

func main() {

    items := map[string]int{
        "coin":   12,
        "chair":  3,
        "pen":    4,
        "bottle": 9,
    }

    keys := make([]string, 0, len(items))

    for key := range items {
        keys = append(keys, key)
    }

    sort.Strings(keys)

    for _, key := range keys {
        fmt.Printf("%s %d\n", key, items[key])
    }
}

为了按键对映射进行排序,我们将键挑选到一个键切片中,对它们进行排序,最后使用这个排序后的切片选取值。

$ go run sort_map_keys.go
bottle 9
chair 3
coin 12
pen 4

按值对地图进行排序

在下一个示例中,我们按值对地图进行排序。该地图存储了圣经中的单词及其频率。

$ wget https://raw.githubusercontent.com/janbodnar/data/main/the-king-james-bible.txt

我们使用钦定版圣经。

package main
import (
    "fmt"
    "io/ioutil"
    "log"
    "sort"
    "strings"
)

func main() {

    fileName := "the-king-james-bible.txt"

    bs, err := ioutil.ReadFile(fileName)

    if err != nil {

        log.Fatal(err)
    }

    text := string(bs)

    fields := strings.FieldsFunc(text, func(r rune) bool {

        return !('a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '\'')
    })

    wordsCount := make(map[string]int)

    for _, field := range fields {

        wordsCount[field]++
    }

    keys := make([]string, 0, len(wordsCount))

    for key := range wordsCount {

        keys = append(keys, key)
    }

    sort.Slice(keys, func(i, j int) bool {

        return wordsCount[keys[i]] > wordsCount[keys[j]]
    })

    for idx, key := range keys {

        fmt.Printf("%s %d\n", key, wordsCount[key])

        if idx == 10 {
            break
        }
    }
}

我们计算KingJames圣经中单词的出现频率。

fields := strings.FieldsFunc(text, func(r rune) bool {

    return !('a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '\'')
})

FieldsFunc按非字母和撇号的字符剪切文本。这也将忽略所有节号。

wordsCount := make(map[string]int)

for _, field := range fields {

    wordsCount[field]++
}

每个单词及其频率都存储在wordsCount映射中。

keys := make([]string, 0, len(wordsCount))

for key := range wordsCount {

    keys = append(keys, key)
}

sort.Slice(keys, func(i, j int) bool {

    return wordsCount[keys[i]] > wordsCount[keys[j]]
})

为了按频率对单词进行排序,我们创建了一个新的keys切片。我们将所有单词放在那里并按它们的频率值对它们进行排序。

for idx, key := range keys {

    fmt.Printf("%s %d\n", key, wordsCount[key])

    if idx == 10 {
        break
    }
}

我们打印出圣经中最常用的前十个单词。

$ go run word_freq.go
the 62103
and 38848
of 34478
to 13400
And 12846
that 12576
in 12331
shall 9760
he 9665
unto 8942
I 8854

去排序结构

在下面的示例中,我们对结构列表进行排序。

package main

import (
    "fmt"
    "sort"
)

type Student struct {
    name  string
    score int
}

func main() {

    students := []Student{

        Student{name: "John", score: 45},
        Student{name: "Bill", score: 68},
        Student{name: "Sam", score: 98},
        Student{name: "Julia", score: 87},
        Student{name: "Tom", score: 91},
        Student{name: "Martin", score: 71},
    }

    sort.Slice(students, func(i, j int) bool {
        return students[i].score < students[j].score
    })

    fmt.Println(students)

    sort.Slice(students, func(i, j int) bool {
        return students[i].name > students[j].name
    })

    fmt.Println(students)
}

我们有一片学生对象,它是从Student结构创建的。我们为sort.Slice提供了两个匿名函数以按分数和姓名对切片进行排序。

$ go run sort_struct.go
[{John 45} {Bill 68} {Martin 71} {Julia 87} {Tom 91} {Sam 98}]
[{Tom 91} {Sam 98} {Martin 71} {Julia 87} {John 45} {Bill 68}]

按多个字段排序

数据可以按多个标准排序。

package main

import (
    "fmt"
    "sort"
)

type Student struct {
    name  string
    score int
}

func main() {

    students := []Student{

        Student{name: "John", score: 87},
        Student{name: "Albert", score: 68},
        Student{name: "Bill", score: 68},
        Student{name: "Sam", score: 98},
        Student{name: "Xenia", score: 87},
        Student{name: "Lucia", score: 87},
        Student{name: "Tom", score: 91},
        Student{name: "Jane", score: 68},
        Student{name: "Martin", score: 71},
        Student{name: "Julia", score: 87},
    }

    sort.Slice(students, func(i, j int) bool {

        if students[i].score != students[j].score {
            return students[i].score < students[j].score
        }

        return students[i].name < students[j].name
    })

    fmt.Println(students)
}

在代码示例中,我们按分数对一部分学生进行排序,当分数相同时,按姓名排序。

sort.Slice(students, func(i, j int) bool {

    if students[i].score != students[j].score {
        return students[i].score < students[j].score
    }

    return students[i].name < students[j].name
})

排序逻辑在匿名函数中实现。首先,我们比较分数。然后,如果分数相同,我们就比较名字。

$ go run sort_mul_fields.go
[{Albert 68} {Bill 68} {Jane 68} {Martin 71} {John 87} {Julia 87}
    {Lucia 87} {Xenia 87} {Tom 91} {Sam 98}]

按日期时间排序

在下面的示例中,我们按日期时间对数据进行排序。

package main

import (
    "fmt"
    "sort"
    "time"
)

type Purchase struct {
    id   string
    date time.Time
}

func main() {

    var purchases = make([]Purchase, 0)
    purchases = append(purchases, Purchase{id: "1", date: time.Now()})
    purchases = append(purchases, Purchase{id: "2", date: time.Now().AddDate(-3, 0, 7)})
    purchases = append(purchases, Purchase{id: "3", date: time.Now().AddDate(2, 4, 7)})
    purchases = append(purchases, Purchase{id: "4",
        date: time.Now().AddDate(-5, -5, -7)})

    sort.Slice(purchases, func(i, j int) bool {
        return purchases[i].date.Before(purchases[j].date)
    })

    for _, pur := range purchases {

        fmt.Printf("%s %s\n", pur.id, pur.date.Format("2 Jan 2006 15:04"))
    }
}

我们有一部分采购。购买按日期时间排序。

sort.Slice(purchases, func(i, j int) bool {
    return purchases[i].date.Before(purchases[j].date)
})

在比较函数中,我们使用Before函数来判断第一次是否在第二次之前。

$ go run sort_date_time.go
4 13 May 2015 14:42
2 27 Oct 2017 14:42
1 20 Oct 2020 14:42
3 27 Feb 2023 14:42

在本教程中,我们在Golang中对数据进行了排序。

列出所有Go教程。

赞(0) 打赏

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏