Go语言泛型示例
涛叔我之前已经介绍过Go语言泛型设计,今天为大家带来一些常用的的泛型代码示例。
切片操作
函数式编程中经常用到 Map/Reduce/Filter 等函数。因为有了泛型,我们也可以编写适应于任意类型的 Map/Reduce/Filter 函数了😂
这三种函数都是用于处理切片数据,所以Go官方本想提供一个标准的 slices 包。但受到了Rob 大佬的反对,所以只能放到exp 包下面了。
// slices 包实现一系列切片算法。
package slices
// Map 使用映射函数将 []T1 转换成 []T2。
// This function has two type parameters, T1 and T2.
// 映射函数 f 接受两个类型类型 T1 和 T2。
// 本函数可以处理所有类型的切片数据。
func Map[T1, T2 any](s []T1, f func(T1) T2) []T2 {
:= make([]T2, len(s))
r for i, v := range s {
[i] = f(v)
r}
return r
}
// Reduce 使用汇总函数将 []T1 切片汇总成一个结果。
func Reduce[T1, T2 any](s []T1, initializer T2, f func(T2, T1) T2) T2 {
:= initializer
r for _, v := range s {
= f(r, v)
r }
return r
}
// Filter 使用过滤函数过滤切片中的数据。
// 该函数返回新的切片,只会保留调用 f 返回 true 的元素。
func Filter[T any](s []T, f func(T) bool) []T {
var r []T
for _, v := range s {
if f(v) {
= append(r, v)
r }
}
return r
}
使用示例如下,所有类型参数均通过推导确定:
:= []int{1, 2, 3}
s
:= slices.Map(s, func(i int) float64 { return float64(i) })
floats // floats 的值为 []float64{1.0, 2.0, 3.0}。
:= slices.Reduce(s, 0, func(i, j int) int { return i + j })
sum // sum 的值为 6。
:= slices.Filter(s, func(i int) bool { return i%2 == 0 })
evens // evens 的值为 []int{2}。
字典操作
下面给出提取任意字典 key 列表的函数。
// maps 包提供能用的字典处理函数。
package maps
// Keys 返回任意字典的 key 组成的切片。
// key 的顺序不确定。
// 本函数接受两个类型参数 K 和 V。
// 因为字典的 key 必须支持相等比较,所以 K 需要满足 comparable 约束。
// 字典的值可以是任意类型。
func Keys[K comparable, V any](m map[K]V) []K {
:= make([]K, 0, len(m))
r for k := range m {
= append(r, k)
r }
return r
}
典型的使用示例如下,类型参数通过推导确定:
:= maps.Keys(map[int]int{1:2, 2:4})
k // 现在 k 的取值是 []int{1, 2} 或者 []int{2, 1}。
集合操作
Go语言的 map 本身就支持不同类型的 K-V。所以一般可以通过 map 实现集合操作。但有了泛型之后,我们也可以开发专门的集合类型。
// sets 包实现集合功能,元素需要支持相等比较。
package sets
// Set 是一组元素的集合。
type Set[T comparable] map[T]struct{}
// Make 构造某类型的集合对象。
func Make[T comparable]() Set[T] {
return make(Set[T])
}
// Add 将 v 添加到集合。
func (s Set[T]) Add(v T) {
[v] = struct{}{}
s}
// Delete 将 v 从集合删除。
func (s Set[T]) Delete(v T) {
delete(s, v)
}
// Contains 查询集合中是否包含 v。
func (s Set[T]) Contains(v T) bool {
, ok := s[v]
_return ok
}
// Len 返回集合元素的数量。
func (s Set[T]) Len() int {
return len(s)
}
// Iterate 遍历集合的每一个元素,执行函数 f。
// 可以以在函数 f 中调用 Delete 方法。
func (s Set[T]) Iterate(f func(T)) {
for v := range s {
(v)
f}
}
使用示例如下:
// 新建整数集合。
// 因为 Make 函数的参数中没有用到类型参数 T,所以无法通过类型推导的方式
// 确定 T 的实际类型,只能手工指定。
:= sets.Make[int]()
s
// 将 1 添加到集合
.Add(1)
s
// 确保集合 s 中不包含数字2
if s.Contains(2) { panic("unexpected 2") }
不过总得来说,使用泛型定义的新集合跟直接使用 map 区别不大。
排序操作
因为没有泛型,Go语言的 sort 包提供了 Float64/Ints/Strings 三种排序函数。如果想对浮点数或者整数切片进行排序,就必须先换成 float64/int 切片,非常麻烦。有了泛型,我们就可以统一处理原始类型的排序算法。
支持排序的切片的元素需要支持比较大小操作。为方便开发者使用,Go官方提供了constraints.Ordered
约束,用来表示所有支持排序的内置类型。所以我们的统一算法可以写成这样:
// orderedSlice 实现了 sort.Interface 接口.
// Less 方法使用 < 运算符。constraints.Ordered 约束确保类型 T 支持 < 运算符。
type orderedSlice[T constraints.Ordered] []T
func (s orderedSlice[T]) Len() int { return len(s) }
func (s orderedSlice[T]) Less(i, j int) bool { return s[i] < s[j] }
func (s orderedSlice[T]) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// OrderedSlice 将切片 s 按照升序排序。
// s 的元素需要支持 < 运算符。
func OrderedSlice[T constraints.Ordered](s []T) {
// 将 s 强转成 orderedSlice[T],这样就可以使用 sort.Sort 进行排序。
.Sort(orderedSlice[T](s))
sort}
使用示例如下:
:= []int32{3, 5, 2}
s1 .OrderedSlice(s1)
sort// 现在 s1 的值为 []int32{2, 3, 5}
:= []string{"a", "c", "b"})
s2 .OrderedSlice(s2)
sort// 现在 s2 的值为 []string{"a", "b", "c"}
sort 包还提供了 Slice 方法,但需要通过闭包引用被排序的切片,不太方便。
type Person struct {
string
Name int
Age }
:= []Person{
people {"Gopher", 7},
{"Alice", 55},
{"Vera", 24},
{"Bob", 75},
}
.Slice(people, func(i, j int) bool { return people[i].Name < people[j].Name }) sort
我们可以利用泛型写一个统一的处理方法:
// sliceFn 实现 sort.Interface 接口。
// Less 方法实际调用 cmp 保存的比较函数。
type sliceFn[T any] struct {
[]T
s func(T, T) bool
cmp }
func (s sliceFn[T]) Len() int { return len(s.s) }
func (s sliceFn[T]) Less(i, j int) bool { return s.cmp(s.s[i], s.s[j]) }
func (s sliceFn[T]) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] }
// SliceFn 根据比较函数 cmp 对切片 s 进行排序。
func SliceFn[T any](s []T, cmp func(T, T) bool) {
.Sort(sliceFn[T]{s, cmp})
sort}
所以原来的比较函数就可以改写成这样:
.SliceFn(s, func(p1, p2 Person) bool { return p1.Name < p2.Name }) sort
Channel 操作
有了泛型,我们就可以将一些常用的 channel 操作统一起来。比如下面的例子:
// chans 包实现一些 channel 操作算法。
package chans
import "runtime"
// Drain 丢弃 chan 的所有元素。
func Drain[T any](c <-chan T) {
for range c {
}
}
// Merge 合并两个 chan,将元素输出到另一个 chan。
func Merge[T any](c1, c2 <-chan T) <-chan T {
:= make(chan T)
r go func(c1, c2 <-chan T, r chan<- T) {
defer close(r)
for c1 != nil || c2 != nil {
select {
case v1, ok := <-c1:
if ok {
<- v1
r } else {
= nil
c1 }
case v2, ok := <-c2:
if ok {
<- v2
r } else {
= nil
c2 }
}
}
}(c1, c2, r)
return r
}
// Ranger 提供一种机制,可以在接收者不再工作的时候通知发送者退出。
//
// Ranger 返回一对 Sender/Receiver。Receiver 提供 Next 方法接收内容。
// Sender 提供 Send 发送内容,并提供 Close 方法停止发送。
// Next 可以检测 Sender 是否关闭。Send 方法可以检测 Receiver 是否退出。
func Ranger[T any]() (*Sender[T], *Receiver[T]) {
:= make(chan T)
c := make(chan bool)
d := &Sender[T]{values: c, done: d}
s := &Receiver[T]{values: c, done: d}
r // 如果接收方已经被回收,则会利用 finalizer 通知发送方。
.SetFinalizer(r, r.finalize)
runtimereturn s, r
}
// Sender 用于给 Receiver 发送消息。
type Sender[T any] struct {
chan<- T
values <-chan bool
done }
// Send 给接收发送消息。发送失败返回 false。
func (s *Sender[T]) Send(v T) bool {
select {
case s.values <- v:
return true
case <-s.done:
// 接收方已退出
return false
}
}
// Close 通知接收方发送消息已经结束。
// 调用 Close 之后就不应该继续使用 Sender 对象。
func (s *Sender[T]) Close() {
close(s.values)
}
// Receiver 用于从 Sender 接收消息。
type Receiver[T any] struct {
<-chan T
values chan<- bool
done }
// Next 返回收到的消息。如果收不到消息,则表明发送方已退出。
func (r *Receiver[T]) Next() (T, bool) {
, ok := <-r.values
vreturn v, ok
}
// finalize 会在 Receiver 销毁的时候通知发送方停止发送。
func (r *Receiver[T]) finalize() {
close(r.done)
}
使用示例见下一小节。
容器定义
有了泛型,我们就可以定义类型安全的容器。而不需要像之前那样来回地转换类型。
这里我们提供一个有序 map 的实现。
// orderedmaps 包基于二叉树实现有序字典。
package orderedmaps
import "chans"
// Map 是有序字典。
type Map[K, V any] struct {
*node[K, V]
root func(K, K) int
compare }
// node 是二叉树中的节点。
type node[K, V any] struct {
k K
v V, right *node[K, V]
left}
// New 构造新字典。
// 因为类型参数 V 只在返回值中使用,无法根据类型推导确认。
// 所以调用 New 函数的时候必须指定所有类型参数的类型。
func New[K, V any](compare func(K, K) int) *Map[K, V] {
return &Map[K, V]{compare: compare}
}
// find 从字典中查询 k 的节点。如果存在,则返回对应的指针;
// 不存在则返回 k 需要保存的位置。
func (m *Map[K, V]) find(k K) **node[K, V] {
:= &m.root
pn for *pn != nil {
switch cmp := m.compare(k, (*pn).k); {
case cmp < 0:
= &(*pn).left
pn case cmp > 0:
= &(*pn).right
pn default:
return pn
}
}
return pn
}
// Insert 加入新的 K-V。
// 已有的值会被覆盖。
// 如果是新 key 则返回 true。
func (m *Map[K, V]) Insert(k K, v V) bool {
:= m.find(k)
pn if *pn != nil {
(*pn).v = v
return false
}
*pn = &node[K, V]{k: k, v: v}
return true
}
// Find 查询 k 对应的值,不存在则返回 V 对应的空值。
// k 不存在则第二个参数返回 false。
func (m *Map[K, V]) Find(k K) (V, bool) {
:= m.find(k)
pn if *pn == nil {
var zero V // 注意空值的用法
return zero, false
}
return (*pn).v, true
}
// keyValue 遍历字典时使用的 k-v 对
type keyValue[K, V any] struct {
k K
v V}
// InOrder 返回中序遍历迭代器。支持并发操作。
func (m *Map[K, V]) InOrder() *Iterator[K, V] {
type kv = keyValue[K, V] // 指定泛型参数,简化类型引用
, receiver := chans.Ranger[kv]()
sendervar f func(*node[K, V]) bool
= func(n *node[K, V]) bool {
f if n == nil {
return true
}
// 如果 sender.Send 返回 false 则停止发送发送,
// 因为这时接收者已经停止工作。
return f(n.left) &&
.Send(kv{n.k, n.v}) &&
sender(n.right)
f}
go func() {
(m.root)
f.Close()
sender}()
return &Iterator[K, V]{receiver}
}
// Iterator 用于遍历有序字典。
type Iterator[K, V any] struct {
*chans.Receiver[keyValue[K, V]]
r }
// Next 返回下一个键值对。如果 bool 值为 false,则表明已经完成迭代。
func (it *Iterator[K, V]) Next() (K, V, bool) {
, ok := it.r.Next()
kvreturn kv.k, kv.v, ok
}
使用示例如下:
import "container/orderedmaps"
var m = orderedmaps.New[string, string](strings.Compare)
// Add 添加 k-v
func Add(a, b string) {
.Insert(a, b)
m}
:= m.InOrder()
i for {
, v, ok := i.Next()
kif !ok {
break
}
// ...
}
Append 函数
Go语言内置 append 函数,可以实现向任意类型切片追加元素的操作。但如果Go语言一开始就支持泛型,就不需要引入这个内置函数了。
// Append 将元素 t 追加到切片 s 的尾总,返回追加后的切片。
// 如果 s 容量够用,则会原地扩展;否则会分配并返回新的切片。
func Append[T any](s []T, t ...T) []T {
:= len(s)
lens := lens + len(t)
tot if tot < 0 {
panic("Append: cap out of range")
}
if tot > cap(s) {
:= make([]T, tot, tot + tot/2)
news copy(news, s)
= news
s }
= s[:tot]
s copy(s[lens:], t)
return s
}
使用方法如下:
:= slices.Append([]int{1, 2, 3}, 4, 5, 6)
s // 效果等同于 s := append([]int{1, 2, 3}, 4, 5, 6)
链表
// lists 实现支持任意类型元素的链表。
package lists
// List 是链表对象。
type List[T any] struct {
, tail *element[T]
head}
// element 保存链表元素信息。
type element[T any] struct {
*element[T]
next
val T}
// Push 在链表尾部追加元素。
func (lst *List[T]) Push(v T) {
if lst.tail == nil {
.head = &element[T]{val: v}
lst.tail = lst.head
lst} else {
.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
lst}
}
// Iterator 支持遍历链表元素。
type Iterator[T any] struct {
**element[T]
next }
// Range 返回迭代对象
func (lst *List[T]) Range() *Iterator[T] {
return Iterator[T]{next: &lst.head}
}
// Next 移动迭代器到下一个元互。
// 如果已经到达尾部则返回 false。
func (it *Iterator[T]) Next() bool {
if *it.next == nil {
return false
}
.next = &(*it.next).next
itreturn true
}
// Val 返回当前元素内容。
// 如果元素为空 bool 值为 false。
func (it *Iterator[T]) Val() (T, bool) {
if *it.next == nil {
var zero T
return zero, false
}
return (*it.next).val, true
}
// Transform 遍历链表 lst 每一个元素,执行函数 f 得到新元素,并保存
// 到新链表中,返回新链表。
func Transform[T1, T2 any](lst *List[T1], f func(T1) T2) *List[T2] {
:= &List[T2]{}
ret := lst.Range()
it for {
if v, ok := it.Val(); ok {
.Push(f(v))
ret}
if !it.Next() {
break
}
}
return ret
}
使用示例如下:
:= lists.List[int]{}
l .Push(1)
l.Push(2)
l
:= Transform(l, func(i int) float64 { return float64(i) })
l2 // 现在 l2 的值是 [1.0, 2.0]
以上就是本文的主要内容。也欢迎关注的我Go语言泛型系列文章。