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 。如果这篇文章对你有帮助,欢迎在生产环境中引入并二次封装本文的代码。也欢迎各位小伙伴提出一些补充或修改建议!

相关推荐
程序员Bears6 分钟前
Django进阶:用户认证、REST API与Celery异步任务全解析
后端·python·django
非晓为骁19 分钟前
【Go】优化文件下载处理:从多级复制到零拷贝流式处理
开发语言·后端·性能优化·golang·零拷贝
北极象23 分钟前
Golang中集合相关的库
开发语言·后端·golang
喵手1 小时前
Spring Boot 中的事务管理是如何工作的?
数据库·spring boot·后端
白总Server2 小时前
Golang领域Beego框架的中间件开发实战
服务器·网络·websocket·网络协议·udp·go·ssl
玄武后端技术栈2 小时前
什么是延迟队列?RabbitMQ 如何实现延迟队列?
分布式·后端·rabbitmq
液态不合群3 小时前
rust程序静态编译的两种方法总结
开发语言·后端·rust
bingbingyihao4 小时前
SpringBoot教程(vuepress版)
java·spring boot·后端
一切皆有迹可循5 小时前
Spring Boot 基于 CAS 实现单点登录:原理、实践与优化全解析
java·spring boot·后端
Kookoos5 小时前
从单体到微服务:基于 ABP vNext 模块化设计的演进之路
后端·微服务·云原生·架构·c#·.net