news 2026/9/13 21:09:03

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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是 lo(Lodash-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.go):

func ValuesK comparable, V any iter.Seq[V]

要点拆解:

  • 类型参数K必须是comparable(map 键的硬性约束),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.go):

func 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 } } } } }

从中可以提炼出三个关键设计:

  1. 惰性求值(lazy evaluation):调用it.Values(m1, m2)本身不会立即遍历任何 map,它只是返回一个闭包。真正的遍历发生在消费者开始for v := range seq时。map 规模再大,构造序列的开销也近乎为零,适合流水线式链式处理。

  2. 及早终止(early termination):闭包接收的yield函数返回bool。当消费者使用breakreturn提前退出循环时,yield返回false,实现随即return停止迭代,避免无谓的遍历。测试 it/lo_test.go 中的assertSeqSupportBreak专门验证了这一点:对it.Values产生的序列执行breakreturn都不会 panic,且序列可被重复遍历(iter.Seq的每次for range都会重新从第一个 map 开始)。

  3. 零中间分配:实现不创建新的切片或缓冲,只是按参数顺序对外层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 值拼接
无 map(nil)nil[]不传参数返回空序列,不 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.go):

  • it.Keys(it/map.go):同样接受多个 map,产出所有键的拼接序列iter.Seq[K],结构与it.Values完全对称。
  • it.UniqValues(it/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.FilterValues(it/map.go):结合谓词筛选后再产出值序列,是FilterValues的混合体,适合"只关心满足条件的值"的场景。
  • it.Entries/it.ToPairs(it/map.go):产出iter.Seq2[K, V]键值对序列;it.Values可以看作对Entries结果丢弃键、只留值的特化。
  • it.Seq2ValueToSeq(it/map.go):把任意iter.Seq2[K, V]转换为iter.Seq[V],与it.Values用途相近但输入是序列而非 map。

从文档目录(docs/data/)可以看出,it-keys.mdit-values.mdit-uniqvalues.mdit-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.Keysit.UniqValuesit.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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 21:06:35

YOLO+大模型:电子元器件检测与智能识别系统实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 21:02:37

系统升级中的数据安全与回滚机制实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 21:00:27

金融AI Agent安全落地:PolarDB与VM沙箱隔离架构实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华