golang实现.NET LINQ式数据查询操作的插件库go-linq的使用
Golang 实现 .NET LINQ 式数据查询操作的插件库 go-linq 的使用
go-linq 是一个强大的 Go 语言集成查询(LINQ)库,它提供了类似 .NET LINQ 的功能。
特性
- 纯 Go 编写,无依赖
- 使用迭代器模式实现完整的惰性求值
- 并发安全
- 支持泛型函数,使代码更清晰且无需类型断言
- 支持数组、切片、映射、字符串、通道和自定义集合
安装
使用 Go modules 时,使用以下导入路径:
go get github.com/ahmetb/go-linq/v3
快速入门
使用方式就像链式调用方法一样简单:
From(slice)
.Where(predicate)
.Select(selector)
.Union(data)
示例 1:查找所有 2015 年后生产的汽车车主
import . "github.com/ahmetb/go-linq/v3"
type Car struct {
year int
owner, model string
}
var owners []string
From(cars).Where(func(c interface{}) bool {
return c.(Car).year >= 2015
}).Select(func(c interface{}) interface{} {
return c.(Car).owner
}).ToSlice(&owners)
或者,你可以使用泛型函数(会有性能损失):
var owners []string
From(cars).WhereT(func(c Car) bool {
return c.year >= 2015
}).SelectT(func(c Car) string {
return c.owner
}).ToSlice(&owners)
示例 2:查找写了最多书籍的作者
import . "github.com/ahmetb/go-linq/v3"
type Book struct {
id int
title string
authors []string
}
author := From(books).SelectMany( // 将作者数组扁平化
func(book interface{}) Query {
return From(book.(Book).authors)
}).GroupBy( // 按作者分组
func(author interface{}) interface{} {
return author // 作者作为键
}, func(author interface{}) interface{} {
return author // 作者作为值
}).OrderByDescending( // 按组长度排序
func(group interface{}) interface{} {
return len(group.(Group).Group)
}).Select( // 从组中获取作者
func(group interface{}) interface{} {
return group.(Group).Key
}).First() // 取第一个作者
示例 3:实现一个自定义方法,只保留大于指定阈值的值
type MyQuery Query
func (q MyQuery) GreaterThan(threshold int) Query {
return Query{
Iterate: func() Iterator {
next := q.Iterate()
return func() (item interface{}, ok bool) {
for item, ok = next(); ok; item, ok = next() {
if item.(int) > threshold {
return
}
}
return
}
},
}
}
result := MyQuery(Range(1,10)).GreaterThan(5).Results()
泛型函数
虽然 Go 没有实现泛型,但通过一些反射技巧,你可以使用 go-linq 而无需处理 interface{}
和类型断言。这会带来性能损失(5-10 倍慢),但会产生更清晰、更易读的代码。
带有 T
后缀的方法(如 WhereT
)接受带有泛型类型的函数。所以不用:
.Select(func(v interface{}) interface{} {...})
你可以这样写:
.SelectT(func(v YourType) YourOtherType {...})
示例 4:在字符串句子切片中实现"MapReduce"以列出最常用的 5 个单词
var results []string
From(sentences).
// 将句子拆分为单词
SelectManyT(func(sentence string) Query {
return From(strings.Split(sentence, " "))
}).
// 对单词分组
GroupByT(
func(word string) string { return word },
func(word string) string { return word },
).
// 按计数排序
OrderByDescendingT(func(wordGroup Group) int {
return len(wordGroup.Group)
}).
// 按单词排序
ThenByT(func(wordGroup Group) string {
return wordGroup.Key.(string)
}).
Take(5). // 取前5个
// 使用索引作为排名来投影单词
SelectIndexedT(func(index int, wordGroup Group) string {
return fmt.Sprintf("Rank: #%d, Word: %s, Counts: %d", index+1, wordGroup.Key, len(wordGroup.Group))
}).
ToSlice(&results)
发布说明
v3.2.0 (2020-12-29)
* Added FromChannelT().
* Added DefaultIfEmpty().
v3.1.0 (2019-07-09)
* Support for Go modules
* Added IndexOf()/IndexOfT().
v3.0.0 (2017-01-10)
* Breaking change: ToSlice() now overwrites existing slice starting
from index 0 and grows/reslices it as needed.
* Generic methods support
- You can now avoid type assertions and interface{}s
- Functions with generic methods are named as "MethodNameT"
* Added ForEach(), ForEachIndexed() and AggregateWithSeedBy().
v2.0.0 (2016-09-02)
* IMPORTANT: This release is a BREAKING CHANGE.
* A COMPLETE REWRITE of go-linq with better performance and memory
efficiency.
* API has significantly changed.
更多关于golang实现.NET LINQ式数据查询操作的插件库go-linq的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang实现.NET LINQ式数据查询操作的插件库go-linq的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
go-linq: Golang 实现 LINQ 式数据查询
go-linq 是一个受 .NET LINQ 启发的 Golang 库,它提供了类似 LINQ 的查询语法来处理集合数据。这个库让 Golang 开发者能够以声明式的方式处理数据集合,类似于 C# 中的 LINQ 查询。
安装
go get github.com/ahmetb/go-linq/v3
基本用法
1. 基本查询操作
package main
import (
"fmt"
"github.com/ahmetb/go-linq/v3"
)
func main() {
// 数据源
numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 查询偶数
var evenNumbers []int
linq.From(numbers).
Where(func(i interface{}) bool {
return i.(int)%2 == 0
}).
ToSlice(&evenNumbers)
fmt.Println("偶数:", evenNumbers) // 输出: [2 4 6 8 10]
// 计算平方
var squares []int
linq.From(numbers).
Select(func(i interface{}) interface{} {
return i.(int) * i.(int)
}).
ToSlice(&squares)
fmt.Println("平方:", squares) // 输出: [1 4 9 16 25 36 49 64 81 100]
}
2. 聚合操作
// 求和
sum := linq.From(numbers).SumInts()
fmt.Println("总和:", sum) // 输出: 55
// 平均值
avg := linq.From(numbers).Average()
fmt.Println("平均值:", avg) // 输出: 5.5
// 最大值
max := linq.From(numbers).Max()
fmt.Println("最大值:", max) // 输出: 10
// 最小值
min := linq.From(numbers).Min()
fmt.Println("最小值:", min) // 输出: 1
3. 复杂对象查询
type Person struct {
Name string
Age int
}
func main() {
people := []Person{
{"Alice", 25},
{"Bob", 30},
{"Charlie", 20},
{"David", 35},
{"Eve", 25},
}
// 按年龄分组
var groups []linq.Group
linq.From(people).
GroupBy(
func(p interface{}) interface{} { return p.(Person).Age }, // 键选择器
func(p interface{}) interface{} { return p.(Person) }, // 元素选择器
).
ToSlice(&groups)
for _, group := range groups {
fmt.Printf("年龄 %d 的人:\n", group.Key)
for _, p := range group.Group {
fmt.Println("-", p.(Person).Name)
}
}
// 排序
var sortedPeople []Person
linq.From(people).
OrderBy(func(p interface{}) interface{} { return p.(Person).Name }).
ToSlice(&sortedPeople)
fmt.Println("按姓名排序:", sortedPeople)
}
4. 其他常用操作
// 去重
numbersWithDup := []int{1, 2, 2, 3, 4, 4, 5}
var distinctNumbers []int
linq.From(numbersWithDup).Distinct().ToSlice(&distinctNumbers)
fmt.Println("去重后:", distinctNumbers) // 输出: [1 2 3 4 5]
// 跳过和获取
var skipped []int
linq.From(numbers).Skip(5).ToSlice(&skipped)
fmt.Println("跳过前5个:", skipped) // 输出: [6 7 8 9 10]
var taken []int
linq.From(numbers).Take(3).ToSlice(&taken)
fmt.Println("取前3个:", taken) // 输出: [1 2 3]
// 连接两个集合
otherNumbers := []int{11, 12, 13}
var concatenated []int
linq.From(numbers).Concat(linq.From(otherNumbers)).ToSlice(&concatenated)
fmt.Println("连接后:", concatenated) // 输出: [1 2 3 4 5 6 7 8 9 10 11 12 13]
5. 延迟执行
go-linq 支持延迟执行,只有在需要结果时才会执行查询:
query := linq.From(numbers).
Where(func(i interface{}) bool {
fmt.Println("过滤:", i)
return i.(int) > 5
}).
Select(func(i interface{}) interface{} {
fmt.Println("映射:", i)
return i.(int) * 2
})
// 此时查询还未执行
fmt.Println("查询已定义,但未执行")
// 执行查询
var result []int
query.ToSlice(&result)
fmt.Println("结果:", result)
性能考虑
虽然 go-linq 提供了方便的查询语法,但它的性能通常不如直接使用 Go 的原生循环。对于性能敏感的代码,建议:
- 对小数据集使用 go-linq 提高代码可读性
- 对大数据集或性能关键路径使用原生 Go 循环
- 考虑使用
ForEach
方法来避免创建中间切片
// 高效遍历
linq.From(numbers).ForEach(func(i interface{}) {
fmt.Println(i.(int))
}, nil)
总结
go-linq 为 Golang 开发者提供了类似 LINQ 的声明式集合操作方式,可以显著提高代码的可读性和简洁性。虽然它在性能上可能不如原生循环,但对于大多数应用场景来说,这种折中是值得的,特别是当代码可维护性比微小的性能差异更重要时。
通过结合 Where、Select、OrderBy、GroupBy 等操作,你可以构建出强大的数据查询管道,以简洁的方式处理复杂的数据转换和过滤需求。