一个能让你少写循环和判断的 Go 开源包,支持泛型
在开发过程中,大家经常会遇到需要处理列表数据的情况。比如,当你从数据库查询用户订单时,查询结果通常会以一个对象列表的形式返回:
[]*Order {
&{
ID: 1,
OrderNo: "20240903628359373756980001"
...
},
...
}
得到了这个结果集后,我们常常需要对其进行各种操作,比如判断某个 ID 为 1 的订单是否存在于列表中:
var exists bool
for _, order := range orders {
if order.ID == 1 {
exists = true
}
}
虽然这种操作不复杂,但经常要手动编写循环和判断逻辑,难免有些繁琐。为了减少循环次数,通常我们会将列表转换为 map
,以订单 ID 为 key:
map[int64]Order{
1: {
ID: 1,
OrderNo: "20240903628359373756980001"
...
}
}
这些列表和哈希表(Slice
和 Map
)操作几乎是我们日常开发中不可避免的工作,常见操作包括:
- 从
Slice
中查找元素的位置 - 判断某个元素是否存在
- 查找所有符合条件的元素
- 将
Slice
转换为Map
- 获取
Map
的所有键或值
例如,JavaScript 中的 map
、reduce
、filter
,以及 Java 的 Stream API 都极大地方便了这些操作。但 Go 标准库并未提供类似的功能,因此很多项目都会手动编写大量的工具函数,比如 InSlice
、XXXInSlice
等等。
然而,Go 1.18 引入了泛型,使得编写这些工具函数变得更加容易和简洁。在此之前,像 go-funk
这样的库就已经提供了大量的函数工具,比如 Contains
、Difference
、IndexOf
、Filter
、ToMap
等。go-funk
是在 Go 1.18 之前发布的,因此为了适应多种类型,它使用了反射。
Go 泛型的引入:lo
库
Go 1.18 支持泛型后,lo
库应运而生,它基于泛型实现,提供了类似于 JavaScript 中 Lodash 的工具函数,例如 map
、filter
、contains
、find
等。相比于 go-funk
,lo
库不再使用反射,效率更高,代码也更加简洁。以 Contains
函数为例:
func Contains[T comparable](collection []T, element T) bool {
for i := range collection {
if collection[i] == element {
return true
}
}
return false
}
利用泛型,只需将 T
约束为可比较类型 comparable
,就可以用 ==
进行比较,整体代码非常简单。
接下来,我将演示一些常见的 Slice
和 Map
操作,展示如何使用 lo
库简化这些任务。
1. Filter
筛选符合条件的子列表
假设有一个订单列表:
[]*Order{
&{
ID: 1,
OrderNo: "20240903628359373756980001",
UserId: 255,
...
},
...
}
我们可以通过 Filter
函数筛选出 UserId
等于指定值的订单:
func FindUserOrders(orders []*Order, userId int64) []*Order {
userOrders := lo.Filter(orders, func(item *Order, index int) bool {
return item.UserId == userId
})
return userOrders
}
2. 从订单列表中提取所有订单 ID
有时我们需要从列表中提取出所有 ID 进行进一步操作,可以使用 Map
函数:
orderIds := lo.Map(orders, func(item *Order, index int) int64 {
return item.ID
})
3. 将列表转换为 Map
为了减少遍历次数,可以将列表转换为以订单 ID 为键的 Map
:
orderMap := lo.SliceToMap(orders, func(item *Order) (int64, *Order) {
return item.ID, item
})
4. 根据字段进行分组
如果需要将订单按 UserId
分组,可以使用 GroupBy
函数:
userOrderMap := lo.GroupBy(orders, func(item *Order) int64 {
return item.UserId
})
5. 计算订单总金额
可以使用 Reduce
函数求出订单的总金额:
totalPrice := lo.Reduce(orders, func(agg int, item *Order, index int) int {
return agg + item.PayMoney
}, 0)
6. 多线程遍历
lo
库还提供了多线程遍历的支持,可以使用 Foreach
函数并发处理集合中的元素:
import lop "github.com/samber/lo/parallel"
lop.ForEach([]string{"hello", "world"}, func(x string, _ int) {
println(x)
})
7. Map
的常用操作
lo
库还提供了 Map
操作的工具函数,例如 Keys
、Values
等。其 API 名称与其他编程语言中类似功能的函数非常接近,便于理解和使用。
使用建议
尽管 lo
库非常强大,但它并不是万能的。在能用简单循环解决问题时,尽量避免过度依赖 lo
。过度使用可能会使代码变得复杂难懂,尤其是嵌套调用时。此外,过度依赖函数式编程风格也可能增加代码的维护难度。
总结
lo
库通过泛型为 Go 带来了简洁高效的工具函数,减少了循环和判断逻辑的编写。如果你的 Go 项目还不支持泛型,可以尝试使用 go-funk
。希望本文能帮助大家更好地理解和使用这些工具库,编写出更加简洁优雅的代码。
更多内容可以参考 lo
的官方文档:https://github.com/samber/lo