大家好,我是小猛哥。清明节快到了,大家假期出行计划的怎么样呢?先别着急出去玩看完再走,求求了~~ 本期继续上一期的话题跟大家聊聊 go 泛型的其他非常实用的优秀实践,废话不多说我们直接开整。
优秀实践
Map 转换方法
-
该方法应用于将输入切片的中的每个元素一一转换其他元素并组织新切片输出。 -
有做过 flink 开发的同学,应该对这个方法极其熟悉。对,就是无转换转换 map/flatmap 算子,作为 ETL 中 Transaction 的基础部分。 -
[T any, M any] 声明多种泛型类型的方式,中间用都好隔开。
package main
import (
"fmt"
)
func mapFunc[T any, M any](a []T, f func(T) M) []M {
n := make([]M, len(a), cap(a))
for i, e := range a {
n[i] = f(e)
}
return n
}
func main() {
vi := []int{1, 2, 3, 4, 5, 6}
vs := mapFunc(vi, func(v int) string {
return "<" + fmt.Sprint(v) + ">"
})
fmt.Println(vs)
}
List 泛型列表
-
整个代码片定义了一个泛型列表类型以及该类型元素入队,相等比较,列表克隆,插入特定位置与删除特定位置等方法。 -
其中 remove 方法的实现比较 tricky,大家看看能否快速反应过来。
/package main
import (
"fmt"
)
type List[T comparable] struct {
l []T
}
func (l *List[T]) Push(v T) {
l.l = append(l.l, v)
}
func (l *List[T]) Insert(v T) {
l.InsertAt(0, v)
return
}
func (l *List[T]) InsertAt(pos int, v T) {
var beforePos []T
copy(beforePos, l.l[:pos])
beforePos = append(beforePos, v)
l.l = append(beforePos, l.l[pos:]...)
return
}
func (l *List[T]) Remove(i int) {
l.l = l.l[:i+copy(l.l[i:], l.l[i+1:])]
return
}
func (l *List[T]) Equals(rhs *List[T]) bool {
if len(l.l) != len(rhs.l) {
return false
}
for i := 0; i < len(l.l); i++ {
if l.l[i] != rhs.l[i] {
return false
}
}
return true
}
func (l *List[T]) Clone() *List[T] {
ll := &List[T]{l: make([]T, len(l.l))}
copy(ll.l, l.l)
return ll
}
func (l *List[T]) Slice() []T {
return l.l
}
func NewList[T comparable](t []T) *List[T] {
return &List[T]{l: t}
}
func main() {
l := NewList([]int{1, 2, 3})
l.Insert(4)
//l.Push(5)
//l.Push(6)
fmt.Println(l.Slice())
l.Remove(0)
fmt.Println(l.Slice())
//c := l.Clone()
//fmt.Println(c.Equals(l))
}
Must 类型推断
-
这段代码片主要展示编译器对泛型类型的推断能力,并如何有效处理掉前置 err。 -
其中 f 变量可以直接调用文件 close() 方法,其实际类型为 *os.File 明显已被编译器推断出来了。
package main
import (
"fmt"
"os"
)
func Must[T any](v T, err error) T {
if err != nil {
panic(err.Error())
}
return v
}
func main() {
f := Must(os.Create("test.txt"))
//defer os.Remove("test.txt")
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
if _, err := f.Write([]byte("hello")); err != nil {
fmt.Println(err)
}
}
Reduce 累加器
-
该方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。熟悉 js 开发的同学也应该了解过同名函数。 -
方法内部创建局部函数而没有使用闭包。值得注意的是泛型函数 reduceFunc 代码前几行返回零值的方式。
package main
import (
"fmt"
)
func reduceFunc[T any](a []T, f func(T, T) T, initial T) T {
if len(a) == 0 || f == nil {
var vv T
return vv
}
l := len(a) - 1
reduce := func(a []T, ff func(T, T) T, memo T, startPoint, direction, length int) T {
result := memo
index := startPoint
for i := 0; i <= length; i++ {
result = ff(result, a[index])
index += direction
}
return result
}
return reduce(a, f, initial, 0, 1, l)
}
func main() {
vi := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := reduceFunc(vi, func(lhs, rhs int) int {
return lhs + rhs
}, 1)
fmt.Println(result)
vs := []string{"x", "y", "z"}
s := reduceFunc(vs, func(lhs, rhs string) string {
return lhs + rhs
}, "a")
fmt.Println(s)
}
uinq 去重方法
-
该方法主要应用于切片内元素的去重。利用hash table 标记重复元素,并返回去重后的切片。 -
支持泛型类型:comparable。这是因为泛型 map 的 key 一定需要可比较是否相等的。因此也对我们输入参数的泛型有了限制。
package main
import (
"fmt"
"math/rand"
"time"
)
func uniq[T comparable](a []T) []T {
u := make([]T, 0, len(a))
m := make(map[T]bool)
for _, v := range a {
if _, ok := m[v]; !ok {
m[v] = true
u = append(u, v)
}
}
return u
}
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(uniq([]int{4, 3, 2, 3, 1, 5, 1}))
}
shuffle 洗牌方法
-
实现经典的洗牌的算法,将输入泛型切片打散后输出,算法复杂度为 o(n).
package main
import (
"fmt"
"math/rand"
"time"
)
func shuffle[T any](a []T) {
n := len(a)
for i := n - 1; i >= 0; i-- {
j := rand.Intn(i + 1)
a[i], a[j] = a[j], a[i]
}
}
func main() {
rand.Seed(time.Now().UnixNano())
vi := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
shuffle(vi)
fmt.Println(vi)
}
总结
本文介绍若干 go1.18 泛型使用的最佳实践示例,在学习的过程中最为关键的还是如何将其应用于实际的项目开发中。
附录
-
下载 go1.18 最新版本的同学请点击这里:https://go.dev/dl/。 -
官方文档学习的同学戳这里:https://go.dev/doc/go1.18。
文章评论