ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

lo 库 it.Values:把多个 map 的值拼接成 Go 1.23 iter.Seq 值序列

lo 库 it.Values:把多个 map 的值拼接成 Go 1.23 iter.Seq 值序列 lo 库 it.Values把多个 map 的值拼接成 Go 1.23 iter.Seq 值序列【免费下载链接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo导读it.Values是 loLodash-style Go 库的迭代器it子包中一个面向 map 值抽取的高阶泛型函数它接受一个或多个map[K]V并把所有 map 的 value 拼接成一个iter.Seq[V]惰性序列。本文以 docs/data/it-values.md 为核心结合 it/map.go 的源码实现与 it/map_test.go 的测试用例完整讲解it.Values的函数签名、多 map 拼接语义、惰性求值原理、与it.Keys/it.UniqValues的配套关系以及在实际业务中的典型用法读完即可在 Go 1.23 项目中直接落地使用。函数签名与核心语义it.Values的完整签名如下见 docs/data/it-values.md 与 it/map.gofunc ValuesK comparable, V any iter.Seq[V]要点拆解类型参数K必须是comparablemap 键的硬性约束V可以是任意类型any。这意味着值可以是结构体、切片、指针、接口等任何 Go 类型。可变参数in ...map[K]V允许传入 0 个、1 个或多个 map。传入 0 个 map 时返回空序列传入多个 map 时按参数顺序依次遍历并拼接它们的值。返回值iter.Seq[V]是 Go 1.23 引入的标准库迭代器类型与for range直接配合使用。顺序语义值的顺序不是确定性的需要特别强调的是it.Values返回的值序列顺序并不保证稳定。原因在于 Go 语言本身规定 map 的迭代顺序是未定义的——源码实现内部直接使用for _, v : range in[i]it/map.go因此值的产出顺序完全取决于底层 map 的迭代顺序。如果你的下游逻辑依赖顺序请先对结果排序见下文排序后使用一节。源码级实现原理it.Values的实现非常精简整个函数体只有一个惰性闭包见 it/map.gofunc ValuesK comparable, V any iter.Seq[V] { return func(yield func(V) bool) { for i : range in { for _, v : range in[i] { if !yield(v) { return } } } } }从中可以提炼出三个关键设计惰性求值lazy evaluation调用it.Values(m1, m2)本身不会立即遍历任何 map它只是返回一个闭包。真正的遍历发生在消费者开始for v : range seq时。map 规模再大构造序列的开销也近乎为零适合流水线式链式处理。及早终止early termination闭包接收的yield函数返回bool。当消费者使用break或return提前退出循环时yield返回false实现随即return停止迭代避免无谓的遍历。测试 it/lo_test.go 中的assertSeqSupportBreak专门验证了这一点对it.Values产生的序列执行break和return都不会 panic且序列可被重复遍历iter.Seq的每次for range都会重新从第一个 map 开始。零中间分配实现不创建新的切片或缓冲只是按参数顺序对外层in切片、对内层每个 map 做双重循环。相比先收集所有值再拼接的传统写法内存占用更优。完整示例从文档到可运行代码文档 docs/data/it-values.md 给出的示例演示了单/多 map 的核心用法完整还原如下m1 : map[string]int{ apple: 1, banana: 2, } m2 : map[string]int{ cherry: 3, date: 4, } valuesSeq : it.Values(m1, m2) var result []int for v : range valuesSeq { result append(result, v) } // result contains values from both maps对应地it/map_example_test.go 提供了带确定性断言的官方示例func ExampleValues() { kv : map[string]int{foo: 1, bar: 2} kv2 : map[string]int{baz: 3} result : slices.Collect(Values(kv, kv2)) sort.Ints(result) fmt.Printf(%v, result) // Output: [1 2 3] }注意官方示例先调用slices.Collect把序列收拢为切片再用sort.Ints排序最后做确定性断言——这正是map 顺序不确定语义下的标准实践。更简洁的惯用写法slices.Collect由于iter.Seq与标准库slices包无缝衔接收集结果可以写得更紧凑import slices values : slices.Collect(it.Values(m1, m2)) // []int{...}顺序不保证单元测试揭示的边界行为it/map_test.go 的TestValues用表格驱动覆盖了五类典型场景是理解函数边界行为的最佳教材测试场景输入期望结果说明单个 map{foo:1, bar:2}[1, 2]基础用法空 map{}[]空 map 不产生任何值多个 map{foo:1,bar:2}, {baz:3}[1, 2, 3]多 map 值拼接无 mapnilnil[]不传参数返回空序列不 panic跨 map 重复键{foo:1,bar:2}, {foo:1,bar:3}[1, 1, 2, 3]不去重键重复不影响值输出最后一行非常关键it.Values不会去重。即使两个 map 拥有相同的键如两处都有foo两个值1都会被输出。若需要去重语义请使用配套的it.UniqValues。与兄弟函数的配套使用在it子包的 map 工具族中it.Values与以下函数形成互补全部定义于 it/map.goit.Keysit/map.go同样接受多个 map产出所有键的拼接序列iter.Seq[K]结构与it.Values完全对称。it.UniqValuesit/map.go对值做去重后再产出序列。实现上先统计所有 map 的总长度total预分配make(map[V]struct{}, total)作为去重集合保留首次出现的值见 it/map_test.go 中preserves first-seen order用例因此要求V comparable。文档 docs/data/it-uniquevalues.md 中有对应说明。it.FilterValuesit/map.go结合谓词筛选后再产出值序列是Filter与Values的混合体适合只关心满足条件的值的场景。it.Entries/it.ToPairsit/map.go产出iter.Seq2[K, V]键值对序列it.Values可以看作对Entries结果丢弃键、只留值的特化。it.Seq2ValueToSeqit/map.go把任意iter.Seq2[K, V]转换为iter.Seq[V]与it.Values用途相近但输入是序列而非 map。从文档目录docs/data/可以看出it-keys.md、it-values.md、it-uniqvalues.md、it-filtervalues.md等属于同一iter → map分组position字段标示了它们在文档侧边栏中的排布顺序。实战示例场景一合并多个配置来源的值列表// 合并多张配置表收集所有版本号 cfgA : map[string]string{redis: 7.2, mysql: 8.0} cfgB : map[string]string{kafka: 3.6, redis: 7.2} versions : slices.Collect(it.Values(cfgA, cfgB)) // 可能得到类似 []string{7.2, 8.0, 3.6, 7.2} 的顺序不确定集合场景二流水线链式处理iter.Seq的惰性特性让it.Values可以嵌入更大的处理链中配合slices/iter标准库函数继续变换import ( iter slices ) scores : map[string]int{alice: 88, bob: 92, carol: 79} // 惰性链先取全部值再筛选及格线以上最后求和 total : 0 for v : range iter.Filter( it.Values(scores), func(v int) bool { return v 60 }, ) { total v }场景三配合去重统计当同一个值出现在多个 map 中时用it.UniqValues得到唯一值集合m1 : map[string]int{foo: 1, bar: 2} m2 : map[string]int{baz: 2} uniq : slices.Collect(it.UniqValues(m1, m2)) // [1, 2]去重后使用前提与注意事项Go 版本要求it子包基于iter标准库源码带有//go:build go1.23构建标签见 it/map.go必须在 Go 1.23 及以上版本使用。主库lo本身声明go 1.18见 go.mod因此it包是作为独立子包按需引入的。导入路径it子包的模块路径为github.com/samber/lo/it使用时需要单独导入例如import github.com/samber/lo/it。顺序不确定性再次强调返回值顺序依赖 Go map 的随机迭代顺序任何依赖顺序的消费方都必须自行排序sort包或slices.Sort。不去重需要唯一值时显式改用it.UniqValues不要依赖it.Values做去重。序列可重复遍历每个iter.Seq每次for range都会从头重新执行闭包遍历逻辑it.Values产出的序列同样如此可放心多次消费。小结it.Values用十余行泛型代码解决了从多张 map 中批量提取值这一高频需求它把多 map 值拼接、惰性求值、及早终止三个特性浓缩为一个返回iter.Seq[V]的函数与 Go 1.23 的 range-over-func 生态无缝衔接。配合it.Keys、it.UniqValues、it.FilterValues以及标准库slices.Collect足以覆盖绝大多数 map 值处理场景。其实现细节与边界行为不去重、顺序不定、零分配、可 break均可在 it/map.go 与 it/map_test.go 中找到源码级佐证值得在阅读源码时反复对照。【免费下载链接】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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进