Go1.18 已经发布,通过例子学习泛型

大家好,我是 polarisxu。

Go1.18 已经发布,泛型也正式面世。也许你不一定要得到泛型,但还是有必要学习下。之前我出过两个泛型相关的教程,没有看的可以看看。

今天通过另外一种方式学习泛型,即具体的例子,建议可以实际动手试验这些例子,同时也领会一下泛型的使用场景。

求最小值、最大值

直接上代码:

package main

import (
	"fmt"

	"golang.org/x/exp/constraints"
)

func max[T constraints.Ordered](s []T) T {
	if len(s) == 0 {
		var zero T
		return zero
	}
	m := s[0]
	for _, v := range s {
		if m < v {
			m = v
		}
	}
	return m
}

func min[T constraints.Ordered](s []T) T {
	if len(s) == 0 {
		var zero T
		return zero
	}
	m := s[0]
	for _, v := range s {
		if m > v {
			m = v
		}
	}
	return m
}

func main() {
	fmt.Println(min([]int{10, 2, 4, 1, 6, 8, 2}))
	fmt.Println(max([]float64{3.2, 5.1, 6.2, 7.6, 8.2, 1.5, 4.8}))
}

注意,golang.org/x/exp/constraints 这个包是实验性的,生产环境代码建议定义自己的一套。

因为要进行大小比较,所以对泛型 T 做了类型约束:constraints.Ordered,只有可进行大小比较的类型才符合类型 T。

2、泛型版 slice 包含函数

标准库中,对字符串、字节数组实现了相关的函数,进行包含判断:strings.Contains 和 bytes.Contains。那对其他基本类型怎么办?比如判断一个 int 是否在一个 int slice 中呢?得自己实现一个。

有了泛型,可以来一个泛型版本:

package main

import "fmt"

func contains[T comparable](elems []T, v T) bool {
    for _, s := range elems {
        if v == s {
            return true
        }
    }
    return false
}

func main() {
    fmt.Println(contains([]string{"a", "b", "c"}, "b"))
    fmt.Println(contains([]int{1, 2, 3}, 2))
    fmt.Println(contains([]int{1, 2, 3}, 10))
}

实际上,golang.org/x/exp/slices 包实现了这样的功能。

3、从 map 中获得 key 的 slice

因为 map 是无序的,但有时候我们需要根据 map 中 key 的顺序做处理,这时候需要提取 map 中的 key 组成一个 slice,然后排序。

不同的类型,都需要做一遍,有了泛型,可以直接出一个泛型版。

package main

import (
    "fmt"
)

func keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

func main() {
    vegetableSet := map[string]bool{
        "potato":  true,
        "cabbage": true,
        "carrot":  true,
    }

    fruitRank := map[int]string{
        1: "strawberry",
        2: "raspberry",
        3: "blueberry",
    }

    fmt.Printf("vegetableSet keys: %+v\n", keys(vegetableSet))
    fmt.Printf("fruitRank keys: %+v\n", keys(fruitRank))
}

输出:

vegetableSet keys: [potato cabbage carrot]
fruitRank keys: [1 2 3]

实际上,golang.org/x/exp/maps 包提供了这样的功能。

4、通用的 slice 排序功能

上面提到了 slice 的排序,这是很常见的需求。Go 标准库提供了排序的包:sort,一般这么用:

package main

import (
	"fmt"
	"sort"
)

func main() {
	ints := []int{5, 2, 6, 3, 1, 4} // unsorted
	sort.Ints(ints)
	fmt.Println(ints)

	s := []string{"Go", "Bravo", "Gopher", "Alpha", "Grin", "Delta"}
	sort.Strings(s)
	fmt.Println(s)
}

标准库 sort 中有看起来差不多的代码。

有了泛型就简单多了,golang.org/x/exp/slices 也提供了相应的实现:

func Sort[E constraints.Ordered](x []E)
func SortFunc[E any](x []E, less func(a, b E) bool)
func SortStableFunc[E any](x []E, less func(a, b E) bool)

对不同的 slice,调用的同一个泛型函数:

package main

import (
	"fmt"

	"golang.org/x/exp/slices"
)

func main() {
	ints := []int{5, 2, 6, 3, 1, 4} // unsorted
	slices.Sort(ints)
	fmt.Println(ints)

	s := []string{"Go", "Bravo", "Gopher", "Alpha", "Grin", "Delta"}
	slices.Sort(s)
	fmt.Println(s)
}

5、filter 功能的实现

函数式编程中,filter



388 字

0001-01-01 08:05 +0805