Go泛型实战:打造优雅的切片工具库

🐉大家好,我是gopher_looklook,现任某独角兽企业Go语言工程师,喜欢钻研Go源码,发掘各项技术在大型Go微服务项目中的最佳实践,期待与各位小伙伴多多交流,共同进步!

Go 语言在 1.18 版本中引入了泛型,这是 Go 语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性。

什么是泛型

泛型是一种编程范式,允许开发者在编写代码时定义通用的类型参数,而不是具体的类型。通过泛型,可以编写出能够处理多种数据类型的代码,而无需为每种类型重复编写相同的逻辑。例如,一个泛型函数可以同时处理整数、浮点数、字符串等多种类型的数据。

泛型解决了什么问题

在 Go 语言引入泛型之前,开发者在处理不同数据类型时,往往需要编写重复的代码。例如,实现一个排序算法,可能需要为整数、浮点数、字符串等分别编写不同的版本。这种重复不仅增加了代码量,也降低了代码的可维护性。引入泛型后,可以通过定义一个通用的类型参数,编写一个通用的排序函数,从而提高代码的复用性和可维护性。

基于泛型的常见切片操作

博主结合自身在实际开发当中的经验,将利用Go泛型,封装一些常见的切片操作。本篇博客所编写的代码,皆可直接集成到生产环境的公共代码库中。各位小伙伴可以根据自身项目的实际情况,将对你们项目有帮助的代码迁移到自己的项目当中。

  1. 反转切片(改变原切片)
go 复制代码
func ReverseOriginalSlice[T any](s []T) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
       s[i], s[j] = s[j], s[i]
    }
}

2.反转切片(不改变原切片)

go 复制代码
func ReverseSlice[T any](s []T) []T {
    res := make([]T, len(s))
    copy(res, s)
    ReverseOriginalSlice(res) // 调用之前的ReverseOriginalSlice函数
    return res
}

3.切片分批

go 复制代码
func BatchSlice[T any](s []T, size int) [][]T {
    var batchSlice [][]T
    // 遍历切片,每次取 size 个元素
    for i := 0; i < len(s); i += size {
       end := i + size
       // 处理最后一批元素数量不足 size 的情况
       if end > len(s) {
          end = len(s)
       }
       // 将当前批次的元素添加到结果中
       batchSlice = append(batchSlice, s[i:end])
    }
    return batchSlice
}

4.合并切片

go 复制代码
func MergeSlices[T any](slices ...[]T) []T {
    totalLength := 0
    for _, slice := range slices {
       totalLength += len(slice)
    }
    res := make([]T, 0, totalLength)
    for _, slice := range slices {
       ls := make([]T, len(slice))
       copy(ls, slice)
       res = append(res, ls...)
    }
    return res
}

5.切片去重

go 复制代码
func UniqueSlice[T comparable](s []T) []T {
    seen := make(map[T]bool)
    res := make([]T, 0, len(s))
    for _, v := range s {
       if !seen[v] {
          // 如果元素未出现过,添加到结果切片中
          res = append(res, v)
          seen[v] = true
       }
    }
    return res
}

6.切片转哈希表

go 复制代码
func SliceToMap[T any, K comparable](s []T, keyFunc func(T) K) map[K]T {
    res := make(map[K]T)
    for _, v := range s {
       key := keyFunc(v)
       res[key] = v
    }
    return res
}

7.哈希表转切片

go 复制代码
func MapToSlice[K comparable, V any, T any](m map[K]V, extractor func(V) T) []T {
    res := make([]T, 0, len(m))
    for _, v := range m {
       res = append(res, extractor(v))
    }
    return res
}

8.获取切片元素的某个字段

go 复制代码
func GetListField[T any, V any](s []T, fieldFunc func(T) V) []V {
    res := make([]V, 0, len(s))
    for _, item := range s {
       res = append(res, fieldFunc(item))
    }
    return res
}

9.切片全部元素满足条件判断

go 复制代码
func SliceMatchCondition[T any](s []T, condition func(T) bool) bool {
    for _, v := range s {
       if !condition(v) {
          return false
       }
    }
    return true
}
  1. 取切片交集
go 复制代码
func Intersection[T comparable](slices ...[]T) []T {
    if len(slices) == 0 {
       return nil
    }

    // 使用 map 来存储第一个切片中的元素
    intersectionMap := make(map[T]int)
    for _, v := range slices[0] {
       intersectionMap[v]++
    }

    // 遍历后续切片,更新交集
    for _, slice := range slices[1:] {
       m := make(map[T]int)
       for _, v := range slice {
          if _, exists := intersectionMap[v]; exists {
             m[v]++
          }
       }
       intersectionMap = m
    }

    // 将交集的元素收集到结果切片中
    var res []T
    for k := range intersectionMap {
       res = append(res, k)
    }

    return res
}
  1. 取切片并集
go 复制代码
func Union[T comparable](slices ...[]T) []T {
    elementMap := make(map[T]struct{})
    for _, slice := range slices {
       for _, v := range slice {
          elementMap[v] = struct{}{}
       }
    }

    var res []T
    for k := range elementMap {
       res = append(res, k)
    }

    return res
}

代码合集

我将上述所有代码集成到一个slices.go文件当中。如果各位小伙伴们的项目有需要,只需将以下代码完整拷贝到你们项目的基础代码工具库即可。

  • slices.go
go 复制代码
package slices

// 反转切片(改变原切片)
func ReverseOriginalSlice[T any](s []T) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
       s[i], s[j] = s[j], s[i]
    }
}

// 反钻切片(不改变原切片)
func ReverseSlice[T any](s []T) []T {
    res := make([]T, len(s))
    copy(res, s)
    ReverseOriginalSlice(res)  
    return res
}

// 切片分批
func BatchSlice[T any](s []T, size int) [][]T {
    var batchSlice [][]T
    
    for i := 0; i < len(s); i += size {
       end := i + size
  
       if end > len(s) {
          end = len(s)
       }
       
       batchSlice = append(batchSlice, s[i:end])
    }
    return batchSlice
}

// 合并切片
func MergeSlices[T any](slices ...[]T) []T {
    totalLength := 0
    for _, slice := range slices {
       totalLength += len(slice)
    }
    res := make([]T, 0, totalLength)
    for _, slice := range slices {
       ls := make([]T, len(slice))
       copy(ls, slice)
       res = append(res, ls...)
    }
    return res
}

// 切片去重
func UniqueSlice[T comparable](s []T) []T {
    seen := make(map[T]bool)
    res := make([]T, 0, len(s))
    for _, v := range s {
       if !seen[v] {
          res = append(res, v)
          seen[v] = true
       }
    }
    return res
}

// 切片转哈希表
func SliceToMap[T any, K comparable](s []T, keyFunc func(T) K) map[K]T {
    res := make(map[K]T)
    for _, v := range s {
       key := keyFunc(v)
       res[key] = v
    }
    return res
}

// 哈希表转切片
func MapToSlice[K comparable, V any, T any](m map[K]V, extractor func(V) T) []T {
    res := make([]T, 0, len(m))
    for _, v := range m {
       res = append(res, extractor(v))
    }
    return res
}

// 获取切片元素的某个字段
func GetListField[T any, V any](s []T, fieldFunc func(T) V) []V {
    res := make([]V, 0, len(s))
    for _, item := range s {
       res = append(res, fieldFunc(item))
    }
    return res
}

// 切片全部元素满足条件判断
func SliceMatchCondition[T any](s []T, condition func(T) bool) bool {
    for _, v := range s {
       if !condition(v) {
          return false
       }
    }
    return true
}

// 取切片交集
func Intersection[T comparable](slices ...[]T) []T {
    if len(slices) == 0 {
       return nil
    }

    intersectionMap := make(map[T]int)
    for _, v := range slices[0] {
       intersectionMap[v]++
    }

    for _, slice := range slices[1:] {
       m := make(map[T]int)
       for _, v := range slice {
          if _, exists := intersectionMap[v]; exists {
             m[v]++
          }
       }
       intersectionMap = m
    }

    var res []T
    for k := range intersectionMap {
       res = append(res, k)
    }

    return res
}

// 取切片并集
func Union[T comparable](slices ...[]T) []T {
    elementMap := make(map[T]struct{})
    for _, slice := range slices {
       for _, v := range slice {
          elementMap[v] = struct{}{}
       }
    }

    var res []T
    for k := range elementMap {
       res = append(res, k)
    }

    return res
}

总结

本文使用Go泛型,对常见的切片操作进行了封装,整理出了一个切片工具库slices.go 。如果这篇文章对你有帮助,欢迎在生产环境中引入并二次封装本文的代码。也欢迎各位小伙伴提出一些补充或修改建议!

相关推荐
Hello.Reader25 分钟前
Rust 中的 Packages 与 Crates:模块化构建的基础
开发语言·后端·rust
韦德说1 小时前
【开源事故】77.7K Star 的 Hugo 作者亲自回信!但他第一句话就让我彻底慌了……
后端·开源·go
爱上语文2 小时前
登录认证(5):过滤器:Filter
java·后端·spring
csucoderlee2 小时前
Go语言中的函数闭包
开发语言·后端·golang
老大白菜2 小时前
使用 Go 语言调用 DeepSeek API:完整指南
后端·golang·deepseek
美味小鱼3 小时前
Rust错误处理:从灭火器到核按钮的生存指南
开发语言·后端·rust
SomeB1oody3 小时前
【Rust自学】19.1. 摆脱安全性限制的unsafe Rust
开发语言·后端·rust
美味小鱼3 小时前
Rust HashMap :当储物袋遇上物品清单
开发语言·后端·rust
码界筑梦坊4 小时前
基于Flask的抖音用户浏览行为分析系统的设计与实现
后端·python·flask·毕业设计