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

1 回复

更多关于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 的原生循环。对于性能敏感的代码,建议:

  1. 对小数据集使用 go-linq 提高代码可读性
  2. 对大数据集或性能关键路径使用原生 Go 循环
  3. 考虑使用 ForEach 方法来避免创建中间切片
// 高效遍历
linq.From(numbers).ForEach(func(i interface{}) {
	fmt.Println(i.(int))
}, nil)

总结

go-linq 为 Golang 开发者提供了类似 LINQ 的声明式集合操作方式,可以显著提高代码的可读性和简洁性。虽然它在性能上可能不如原生循环,但对于大多数应用场景来说,这种折中是值得的,特别是当代码可维护性比微小的性能差异更重要时。

通过结合 Where、Select、OrderBy、GroupBy 等操作,你可以构建出强大的数据查询管道,以简洁的方式处理复杂的数据转换和过滤需求。

回到顶部