lo 库 it.FilterValues 详解:Go 1.23 迭代器风格的 map 值过滤函数
【免费下载链接】lo💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo
本文聚焦 lo 库 it 包中的 FilterValues 函数,讲解其函数签名、谓词用法与全部示例场景,并结合 it/map.go 的源码实现、it/map_test.go 的测试用例,说明它作为iter.Seq惰性序列的工作原理,以及与 core 包中同步切片版本 lo.FilterValues 的差异,帮助读者在 Go 1.23+ 的迭代器场景下正确选择和使用这一工具函数。
一、函数定位与核心语义
it.FilterValues是 lo 库面向 Go 1.23 引入的iter标准库设计的序列化工具之一。其官方文档描述为:
Filters map values based on a predicate function that takes both key and value. Returns a slice of values that satisfy the predicate.
(根据一个同时接收键和值的谓词函数过滤 map 的值,返回满足谓词的值切片。)
函数签名如下,定义在 it/map.go:
func FilterValuesK comparable, V any bool) iter.Seq[V]关键参数说明:
| 参数 | 约束/类型 | 说明 |
|---|---|---|
K | comparable | map 键类型,要求可比较 |
V | any | map 值类型,任意类型 |
in | map[K]V | 待过滤的输入 map |
predicate | func(key K, value V) bool | 谓词函数,同时接收键与值,返回true时该值被保留 |
| 返回值 | iter.Seq[V] | 惰性序列,可被range遍历或slices.Collect收集为切片 |
从源码注释看,FilterValues被明确定位为 “a mix of Filter and Values”——即Filter(按条件筛选)与Values(提取 map 值)两个能力的组合:遍历时对每个键值对执行谓词判断,只有判断为真时才把值 yield 出去。
二、完整示例场景
以下示例完整继承自官方文档 docs/data/it-filtervalues.md,覆盖数值过滤、字符串长度过滤、键值联合判断、空 map 与浮点数过滤五类典型场景。由于it包基于 Go 1.23 的iter.Seq实现,实际使用时通常先用slices.Collect把序列收集为切片,再断言结果:
package main import ( "fmt" "iter" "slices" "strings" "github.com/samber/lo/it" ) func collectV any []V { return slices.Collect(seq) } func main() { // 场景 1:数值过滤,取出值大于 2 的元素 m := map[string]int{ "apple": 3, "banana": 5, "cherry": 2, "date": 0, } result := collect(it.FilterValues(m, func(key string, value int) bool { return value > 2 })) // 值为 3 和 5(对应 "apple" 和 "banana"),元素顺序不保证 // 场景 2:字符串长度过滤,取出长度为 3 的值 numberMap := map[int]string{1: "one", 2: "two", 3: "three", 4: "four"} result = collect(it.FilterValues(numberMap, func(key int, value string) bool { return len(value) == 3 })) // "one"、"two"、"three" // 场景 3:键与值联合判断,键以 "a" 开头且年龄 >= 20 personMap := map[string]int{"alice": 25, "bob": 30, "charlie": 17} result = collect(it.FilterValues(personMap, func(key string, age int) bool { return strings.HasPrefix(key, "a") && age >= 20 })) // 仅 "alice" 的值 25 // 场景 4:空 map,谓词恒为 true 也不产生任何元素 emptyMap := map[string]int{} result = collect(it.FilterValues(emptyMap, func(key string, value int) bool { return true })) // 空切片 // 场景 5:浮点数过滤,只保留正值 dataMap := map[string]float64{"a": 1.5, "b": -2.0, "c": 0.0, "d": 3.14} result = collect(it.FilterValues(dataMap, func(key string, value float64) bool { return value > 0 })) // 1.5 和 3.14 fmt.Println(result) }需要特别注意的是:文档示例注释中把结果写作[]int{3, 5}这类形式,但由于 Go map 本身的遍历顺序是随机的,FilterValues输出序列中元素的相对顺序并不保证与 map 键的某种排列一致。因此对结果做精确切片比较时,建议先排序或使用ElementsMatch之类的无序断言。
三、源码级实现剖析
it.FilterValues的完整实现见 it/map.go:
// FilterValues transforms a map into a sequence based on predicate returns true for specific elements. // It is a mix of Filter and Values. func FilterValuesK comparable, V any bool) iter.Seq[V] { return func(yield func(V) bool) { for k, v := range in { if predicate(k, v) && !yield(v) { return } } } }从源码结构看,其实现包含三个值得关注的细节:
- 返回的是闭包而非切片。函数体并不立即遍历 map,而是返回一个捕获了
in与predicate的函数值。只有当调用方真正range这个序列时,map 遍历才会发生,这是一种惰性求值(lazy evaluation)模式。 - 对
yield返回值的检查实现了提前终止。yield(v)返回false时闭包立刻return,这意味着消费方在迭代中途break或return时,谓词不会在剩余键值对上被白白执行。这一点在测试基础设施 it/lo_test.go 的assertSeqSupportBreak辅助函数中有系统性验证:
// assertSeqSupportBreak checks whether it is possible to break iteration over a [iter.Seq]. func assertSeqSupportBreakT any iter.Seq[T] { t.Helper() assert.NotPanics(t, func() { for range seq { break } for range seq { return } }) return seq }- 该实现受编译条件约束。it/map.go 文件首行带有
//go:build go1.23构建标签,因此it包的所有函数(包括FilterValues)只在 Go 1.23 及以上工具链下参与编译。项目整体 go.mod 声明的最低版本是 Go 1.18,即 core 包兼容 Go 1.18+,而it包是面向 Go 1.23 迭代器标准库的增量扩展。
四、与 core 包 lo.FilterValues 的对照
同一个仓库中,core 包提供了同步切片版本的 lo.FilterValues,其签名与文档中的 core 版一致:
// FilterValues transforms a map into a slice based on predicate returns true for specific elements. // It is a mix of lo.Filter() and lo.Values(). func FilterValuesK comparable, V any bool) []V { result := make([]V, 0, len(in)) for k, v := range in { if predicate(k, v) { result = append(result, v) } } return result }两个版本的对应关系可以归纳为:
| 维度 | lo.FilterValues(core,map.go) | it.FilterValues(it/map.go) |
|---|---|---|
| 返回值 | []V,立即物化为切片 | iter.Seq[V],惰性序列 |
| 执行时机 | 调用即遍历整个 map | 被 range/Collect 时才遍历 |
| 提前终止 | 不支持(谓词必然跑完所有条目) | 支持(yield 返回 false 即停止) |
| 运行环境 | Go 1.18+(泛型即可) | Go 1.23+(依赖标准库iter包) |
| 典型搭配 | 直接使用 | slices.Collect收集,或与其他iter.Seq组合 |
如果下游逻辑需要错误处理能力,core 包还有带错误返回的 FilterValuesErr:谓词签名变为func(key K, value V) (bool, error),谓词一旦返回非空 error,迭代立即停止并返回该错误。而it包的 map.go 目前提供的是无错误变体的FilterValues;从源码结构看,若需要“错误 + 迭代器”的组合,可以先用 core 的FilterValuesErr收集为切片,再按需转换。
五、相关函数与组合使用
在 it/map.go 中,FilterValues与一族 map 序列化工具紧邻定义,可自然组合:
- FilterKeys:与
FilterValues对称,返回满足谓词的键序列,即 “Filter + Keys” 的组合; - Values:不带谓词,返回全部值的序列。
FilterValues可以理解为Values加上了按 key/value 判断的闸门; - Keys、Entries:分别返回键序列与键值对序列,是 map 到序列转换的基础工具;
- FilterMapToSeq:谓词升级为转换函数
func(key K, value V) (R, bool),在过滤的同时做类型变换,功能上是FilterValues的泛化。
一个典型的组合用法:先用FilterValues筛出值序列,再交给it包的其他序列函数做二次处理,例如排序后取前 N 项,整个过程无需中间切片分配。
六、测试用例验证
it.FilterValues的行为在 it/map_test.go 中有专门的单元测试TestFilterValues,包含两个子场景:
func TestFilterValues(t *testing.T) { t.Parallel() t.Run("string values filtered by matching predicate", func(t *testing.T) { t.Parallel() is := assert.New(t) result1 := FilterValues(map[int]string{1: "foo", 2: "bar", 3: "baz"}, func(k int, v string) bool { return v == "foo" }) assertSeqSupportBreak(t, result1) is.Equal([]string{"foo"}, slices.Collect(result1)) }) t.Run("int values with predicate always false", func(t *testing.T) { t.Parallel() is := assert.New(t) result2 := FilterValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, func(k string, v int) bool { return false }) is.Empty(slices.Collect(result2)) }) }第一个子用例验证正常过滤路径(只保留值为"foo"的条目),并在收集结果前先执行assertSeqSupportBreak,确认中途break/return不会导致 panic;第二个子用例验证谓词恒为false时序列为空。此外,it/map_example_test.go 提供了可直接运行的示例测试:
func ExampleFilterValues() { kv := map[int]string{1: "foo", 2: "bar", 3: "baz"} result := slices.Collect(FilterValues(kv, func(k int, v string) bool { return v == "foo" })) fmt.Printf("%v", result) // Output: [foo] }运行go test ./it/即可复现上述验证(需 Go 1.23 工具链,以满足it/map.go的构建标签)。
七、使用要点小结
- 版本前提:
it.FilterValues依赖 Go 1.23 的标准库iter包,文件头部的构建标签见 it/map.go;低版本环境应改用 core 包 map.go 中的同步切片版本。 - 惰性语义:返回的
iter.Seq[V]可被多次range,每次都会重新遍历底层 map 并重新执行谓词;若需要固化结果,用slices.Collect一次性收集。 - 顺序不确定:结果顺序继承自 map 的随机遍历顺序,做断言或对外输出时不要假设固定排列。
- 谓词同时拿到 key 和 value:这是
FilterValues相对“先Values再filter”两步写法的核心优势——单步内即可完成键值联合判断,且只遍历 map 一次。 - 组合方向:与 FilterKeys、Entries、FilterMapToSeq 等函数共同构成 it 包的 map 序列化工具体系;需要错误处理时参考 core 包 FilterValuesErr 的设计。
【免费下载链接】lo💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考