
lo it.Chunk基于 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本文围绕lo库it包中的Chunk函数展开它如何把一个iter.Seq[T]惰性切分为固定长度大小的子序列、为什么size 0会直接 panic、空序列输入时为什么不会产出任何 chunk以及它与lo.Chunk急切切片版、it.ChunkString、it.ChunkEntries在边界行为上的差异。读完后你可以理解 Go 1.23 迭代器范式下的分块实现技巧并知道在什么场景下该选it.Chunk还是lo.Chunk。函数签名与适用前提it.Chunk的完整签名为见 docs/data/it-chunk.mdfunc ChunkT any iter.Seq[[]T]参数与返回值的含义参数 / 返回说明collection iter.Seq[T]任意可迭代序列T无约束可以是[]T元素、map派生序列、管道产生的数据等size int每个 chunk 的目标长度必须大于 0否则调用处立即 panic返回值iter.Seq[[]T]惰性序列每次 yield 一个[]T子块最后一块可以小于size两个适用前提需要注意Go 版本要求it包基于 Go 1.23 引入的iter.Seq类型it/seq.go 文件头部带有//go:build go1.23构建标签第 1 行。也就是说该文件只在 Go 1.23 工具链下参与编译而lo主包本身仍从 Go 1.18 起步见 go.mod。如果你的项目使用 Go 1.181.22应改用主包中的急切版本lo.Chunkslice.go。惰性语义返回的iter.Seq[[]T]在被range之前不会执行任何分块逻辑也不会分配任何 chunk 缓冲区。基本用法文档给出的核心示例是把一个整数序列按每 2 个一组切分seq : func(yield func(int) bool) { _ yield(1) _ yield(2) _ yield(3) _ yield(4) _ yield(5) } chunks : it.Chunk(seq, 2) var result [][]int for chunk : range chunks { result append(result, chunk) } // result contains [1, 2], [3, 4], [5]仓库中的官方示例测试 it/seq_example_test.go 演示了更典型的来源——用slices.Values把切片转成序列func ExampleChunk() { list : slices.Values([]int{0, 1, 2, 3, 4}) result : it.Chunk(list, 2) for r : range result { fmt.Printf(%v\n, r) } // Output: // [0 1] // [2 3] // [4] }可以看出5 个元素、size2时前两块为[0 1]、[2 3]最后一块为不足size的[4]与“最后块可以更小”的语义一致。源码实现解析it.Chunk的完整实现位于 it/seq.go// Chunk returns a sequence of elements split into groups of length size. If the sequence cant be split evenly, // the final chunk will be the remaining elements. // Play: https://go.dev/play/p/qo8esZ_L60Q func ChunkT any iter.Seq[[]T] { if size 0 { panic(it.Chunk: size must be greater than 0) } return func(yield func([]T) bool) { var newSlice []T for item : range collection { if newSlice nil { newSlice make([]T, 0, size) } newSlice append(newSlice, item) if len(newSlice) size { if !yield(newSlice) { return } newSlice nil } } if newSlice ! nil { yield(newSlice) } } }从源码结构看实现上有 4 个关键设计点参数校验前置且采用 panic 策略size 0的校验发生在函数入口、闭包返回之前第 291-293 行。这意味着只要调用it.Chunk(seq, 0)就会立刻 panicpanic 消息固定为it.Chunk: size must be greater than 0。测试 it/seq_test.go 用is.PanicsWithValue精确断言了这个错误文案同族的it.ChunkString、it.ChunkEntries、lo.Chunk均遵循同一约定。惰性缓冲按需分配内部维护一个newSlice变量首个元素到来时才执行make([]T, 0, size)分配缓冲区且 cap 直接取size避免每 chunk 内的重复扩容。序列为空时newSlice始终为nil因此空输入不产生任何 chunk也不发生任何分配。满块即发、发后即弃当len(newSlice) size时 yield 该块随后把newSlice置回nil。已 yield 出去的切片引用交给消费方函数本身不再持有不会造成对上游大序列的整体保留从实现上看不存在“整序列驻留内存”的问题这是它与it.Reverse、it.Shuffle等需要slices.Collect全量物化的辅助函数的重要区别。尾块收尾主循环结束后用if newSlice ! nil { yield(newSlice) }把不足size的剩余元素作为最后一个 chunk 发出注意此处不检查返回值——按iter.Seq协议yield 在序列末尾的行为由消费方决定这里直接结束闭包即可。另外闭包内部对yield返回false的处理第 303-305 行保证了支持中途 breakrange提前退出时分块逻辑立即停止不会继续消费上游序列。it/seq_test.go 中的assertSeqSupportBreak专门验证了该行为。边界行为测试用例覆盖的四类场景it.Chunk的行为契约由 it/seq_test.go 的TestChunk完整定义场景输入 / 参数期望结果均分[0 1 2 3 4 5]size2[[0 1] [2 3] [4 5]]不均分[0 1 2 3 4 5 6]size2末块为[6]空序列[]size2不产出任何 chunkslices.Collect得到nil单元素[0]size2[[0]]单块且小于size非法 size任意输入size0panic消息为it.Chunk: size must be greater than 0其中“空序列 → 无 chunk”这一条尤其值得注意因为它正是文档 Note 一节所强调的语义分界点见下节。与相关分块函数的对比及空输入不一致问题it.Chunk在lo的辅助函数族中有一个“切片急切版”兄弟和一个“字符串版”三者边界行为并不完全一致与 lo.Chunk切片急切版对比lo.Chunk定义在 slice.go签名为func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice。两者的核心差异执行时机lo.Chunk立即遍历整个切片并返回[]Slice还会预计算chunksNum以便一次分配结果切片it.Chunk返回惰性序列配合for range消费时才逐块产出。内存模型lo.Chunk对每个块执行copy到新切片源码注释明确说明是为了“prevent memory leak and free memory from initial collection”即让原切片可被 GC 回收it.Chunk每块独立make天然与上游解耦。适用对象lo.Chunk只接受切片Slice ~[]Tit.Chunk接受任意iter.Seq[T]例如 channel 派生序列、slices.Values、其他辅助函数返回的序列可自由串联Filter、Map等it包操作。与 it.ChunkEntriesmap 分块版对比it/map.go 中的ChunkEntriesK comparable, V any iter.Seq[map[K]V]实现了与it.Chunk同构的逻辑nil 缓冲 → 满 size 即 yield → 尾块收尾size 0同样 panic只是作用对象是 map 的 entries。由于 Go map 遍历顺序不确定ChunkEntries产出的分组内容本身也是非确定的这一点与顺序确定的it.Chunk有本质区别。空输入不一致it.Chunk 与 it.ChunkString 的行为差异这是 docs/data/it-chunk.md Note 一节明确指出的已知问题也直接写在 it/string.go 的源码注释里it.ChunkStringandit.Chunkfunctions behave inconsistently for empty input:it.ChunkString(, n)returns[]instead of[].对照源码即可验证it.Chunkit/seq.go空序列时newSlice恒为nil一个 chunk 都不产出it.ChunkStringit/string.go闭包开头就判断if len(str) 0 || size len(str) { yield(str); return }于是ChunkString(, 2)会 yield 一次空字符串结果是[]。因此在处理“输入可能为空”的场景时如果代码同时依赖这两个函数必须自行归一化空输入的预期例如消费it.ChunkString结果时过滤空串或统一改用it.Chunk处理元素序列。官方将此差异登记在上游 issue samber/lo#788 中阅读代码时应以当前仓库实现为准。与滑动窗口类函数的选型区分在it包的序列工具中Chunk属于“不重叠、不遗漏”的连续分块。如果需求是重叠窗口应选用 it/seq.go 中的WindowSliding(collection, size, 1)相邻窗口重叠size-1个元素且只产出满窗口或更通用的Sliding(collection, size, step)支持重叠与间隔。如果需求是按条件动态分组组边界由谓词决定而非固定长度则应使用PartitionByit/seq.go。固定长度分块、滑动窗口、条件分组三者的边界语义互不替代。小结it.Chunk是lo库针对 Go 1.23iter.Seq的惰性分块函数size 0前置 panic空输入零产出尾块可小于size支持消费方中途 break逐块分配不驻留上游数据行为契约以 it/seq_test.go 为准官方可运行示例见 it/seq_example_test.go输入是普通切片且环境为 Go 1.18 时等价替代是 slice.go 的lo.Chunk对 map 分块用it.ChunkEntries对字符串分块用it.ChunkString——但注意后者的空输入行为与it.Chunk不一致[]vs 无产出。【免费下载链接】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),仅供参考