
lo 库 Compact 函数详解基于 Go 泛型的高效零值过滤【免费下载链接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lolo.Compact是 Lodash 风格 Go 泛型库 lo 中一个极其实用的切片工具函数它接收一个切片返回其中所有非零值元素组成的新切片常用于数据清洗、日志与配置过滤等场景。本文将以 docs/data/core-compact.md 文档为骨架结合 slice.go 源码实现与 slice_test.go 测试用例从函数签名、底层原理、类型边界、使用场景到性能基准完整剖析Compact的方方面面。读完本文你将掌握Compact的全部用法、与Filter/Reject/Without等相似函数的取舍以及它在各种数据类型下的行为差异。Compact 是什么Compact是 lo 库核心包中面向切片slice的工具函数。原文档对其的定义只有一句话Returns a slice of all non-zero elements即返回由所有非零元素组成的切片。它的核心价值在于当我们需要从切片中剔除空值时不必手写循环判断每个元素是否为零值一行调用即可完成。官方文档给出的经典示例lo.Compact([]string{, foo, , bar, }) // []string{foo, bar}字符串切片中的空串全部被剔除只保留了foo和bar两个非零元素。函数签名与泛型约束原文档 frontmatter 中记录了完整函数签名func Compact[T comparable, Slice ~[]T](collection Slice) Slice对比 slice.go 中的实际实现签名完全一致。这个签名里有三个值得注意的泛型设计点T comparable约束元素类型必须是可比较类型comparable即支持/!运算的类型。因为Compact需要通过与零值比较来判断元素是否为空而比较操作要求类型可比较。Go 中的基本类型int、string、bool、float 等、指针、channel 以及所有字段均可比较的结构体都满足该约束但包含 slice、map、func 字段的结构体不可比较不能直接作为Compact的元素类型。Slice ~[]T与返回值Slice~波浪号表示底层类型约束意味着任何底层类型为[]T的命名切片类型都能被接受且返回类型保持为调用者的原始类型而不是退化成[]T。这正是 lo 库类型保真设计理念的体现。不接收谓词函数与Filter不同Compact不需要传入任何回调函数它只根据零值这一固定规则进行过滤零判断逻辑完全内置于实现中。命名切片类型的类型保真slice_test.go 中专门验证了这一点type myStrings []string allStrings : myStrings{, foo, bar} nonempty : Compact(allStrings) is.IsType(nonempty, allStrings, type preserved)自定义类型myStrings经过Compact处理后返回值的动态类型仍然是myStrings而非[]string。对于依赖具体命名类型的代码例如自定义的type Tags []string等业务类型这一特性可以避免额外的类型断言或转换让Compact能无缝嵌入既有的类型体系。源码实现解析slice.go 中Compact的完整实现非常简洁// Compact returns a slice of all non-zero elements. // Play: https://go.dev/play/p/tXiy-iK6PAc func Compact[T comparable, Slice ~[]T](collection Slice) Slice { var zero T result : make(Slice, 0, len(collection)) for i : range collection { if collection[i] ! zero { result append(result, collection[i]) } } return result }其底层逻辑可以拆解为三步声明零值var zero T声明元素类型的零值。对于不同类型零值分别为数值类型为0、字符串为、布尔为false、指针/接口为nil、结构体为所有字段均为零值的结构体。预分配容量make(Slice, 0, len(collection))按照输入切片长度预分配底层数组容量。由于最坏情况下所有元素都非零结果与输入等长预分配可以避免append过程中反复扩容保证单次内存分配、线性时间复杂度O(n)。零值比较过滤遍历输入切片将每个元素与零值比较不相等则追加到结果切片。由于比较操作是O(1)的常数时间操作整体复杂度为O(n)空间复杂度同样为O(n)结果切片容量。一个值得注意的细节不修改原切片实现中通过makeappend创建了全新的结果切片不会修改调用者传入的原始切片这一点与 mutable 子包中的原地修改风格如mutable.Filter形成鲜明对比。如果你需要保留原数据用于后续处理Compact是安全的选择。各类型的零值行为零值过滤这一规则在具体类型上的表现需要细致区分slice_test.go 中的TestCompact对四类典型场景做了完整覆盖。数值与字符串r1 : Compact([]int{2, 0, 4, 0}) assert.Equal(t, []int{2, 4}, r1) r2 : Compact([]string{, foo, , bar, }) assert.Equal(t, []string{foo, bar}, r2)数值类型剔除0字符串剔除空串。注意0.0、这类语义上的空与零值等价都会被剔除。布尔值r3 : Compact([]bool{true, false, true, false}) assert.Equal(t, []bool{true, true}, r3)布尔切片中false会被剔除只保留true。这在处理标志位集合类数据时需要特别留意Compact的结果只包含true如果你想保留false则不应使用本函数。结构体type foo struct { bar int baz string } r4 : Compact([]foo{ {bar: 1, baz: a}, // 所有字段均为非零值 {bar: 0, baz: }, // 所有字段均为零值 → 被剔除 {bar: 2, baz: }, // bar 非零 → 保留 }) assert.Equal(t, []foo{{bar: 1, baz: a}, {bar: 2, baz: }}, r4)结构体的零值 所有字段都为零值。因此{bar: 0, baz: }会被剔除而{bar: 2, baz: }只要有一个字段非零就会保留。换句话说Compact对结构体是整体零值判定而不是逐字段判定——如果你需要剔除部分字段为空的元素应该改用Filter 自定义谓词。指针易踩坑点e1, e2, e3 : foo{bar: 1, baz: a}, foo{bar: 0, baz: }, foo{bar: 2, baz: } // 注意e2 是 foo 的零值但 e2 不是 *foo 的零值 r5 : Compact([]*foo{e1, e2, nil, e3}) assert.Equal(t, []*foo{e1, e2, e3}, r5)指针类型的零值是nil因此Compact只剔除nil指针。测试注释特别强调了一个反直觉的细节即使e2指向的结构体是零值e2这个指针本身非 nil所以依然会被保留。若想同时剔除指向零值结构体的指针需要Filter配合解引用判断Compact无法完成。与相似函数家族的对比原文档similarHelpers元数据列出了四个相似函数Filter、Reject、Without、WithoutEmpty。理解它们的差异有助于在合适的场景选用合适的工具。函数签名要点过滤规则是否需要回调Compactfunc Compact[T comparable, Slice ~[]T](collection Slice) Slice剔除零值元素否Filterfunc Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice保留谓词返回 true 的元素是Rejectfunc Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice剔除谓词返回 true 的元素是Withoutfunc Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice剔除与指定值相等的元素否WithoutEmptyfunc WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice同 Compact否Filter自定义判定规则core-filter.md 对应的Filter需要传入谓词函数适合非零之外的任意过滤逻辑even : lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool { return x%2 0 }) // []int{2, 4}Compact可以视为Filter的一个特例Filter(collection, lo.IsNotEmpty)。Reject反向过滤slice.go 中的Reject语义与Filter相反剔除谓词为 true 的元素。如果需要保留非零元素用Compact更简洁如果需要剔除满足复杂条件的元素用Reject。Without剔除指定值intersect.go 中的Without接收变长参数exclude ...T可精确剔除指定的一个或多个值不限于零值例如剔除所有-1占位符。它适用于剔除特定值而非剔除零值的场景。WithoutEmpty已被废弃的别名intersect.go 中的WithoutEmpty直接委托给Compact// Deprecated: Use lo.Compact instead. func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice { return Compact(collection) }源码注释明确标注了Deprecated官方建议统一使用Compact。新代码请直接使用lo.Compact不要再用WithoutEmpty。典型使用场景结合 lo 库在数据处理中的定位Compact的典型场景包括数据清洗从用户输入、外部 API 返回的切片中剔除空串、0 值例如清洗标签列表Compact([]string{, go, , generics})→[go, generics]。去空统计在求和、求均值前先剔除 0 值元素避免污染统计结果。布尔标志收敛从一批开关状态中只保留已开启true项。指针切片瘦身从对象指针列表中剔除nilGC 友好、避免后续解引用 panic。性能与基准测试benchmark 目录为Compact提供了对应的基准测试 core_slice_bench_test.gofunc BenchmarkCompact(b *testing.B) { for _, n : range lengths { ints : genSliceInt(n) b.Run(fmt.Sprintf(ints_%d, n), func(b *testing.B) { for i : 0; i b.N; i { _ lo.Compact(ints) } }) } }测试按不同数据规模lengths定义于 helpers_test.go分别跑分。结合实现来看Compact的性能特征是单次线性遍历O(n)时间开销无嵌套循环、无额外排序或哈希。预分配避免扩容make(Slice, 0, len(collection))确保全程最多一次内存分配。零回调开销相比Filter/RejectCompact没有谓词函数调用在热路径上节省了函数调用开销。可以使用仓库 Makefile 提供的基准命令验证例如make bench或go test -bench BenchmarkCompact ./benchmark实测时Compact在常见切片规模下通常表现出非常低的常数开销。由于仓库未公开具体基准数据这里不做任何数值引用读者可自行在本地复现测量。关联的迭代器版本值得一提的是lo 库的迭代器子包 it/seq.go 中也提供了序列iter.Seq版本的Compact// Compact returns a sequence of all non-zero elements. func CompactT comparable, I ~func(func(T) bool) I { return Filter(collection, lo.IsNotEmpty) }它直接基于Filter与IsNotEmpty组合实现适用于 Go 1.23 的iter序列流式处理场景其对应文档见 it-compact.md。如果你的代码走的是迭代器风格的数据管道可以直接复用同名 API保持语义一致。小结Compact是 lo 库切片工具集中小而美的代表签名func Compact[T comparable, Slice ~[]T](collection Slice) Slice泛型约束comparable 底层类型约束~[]T返回原命名类型语义剔除所有零值元素0、、false、nil、全零字段结构体保留其余元素不修改原切片复杂度O(n)时间、O(n)空间预分配 无回调性能开销极低边界指针类型只剔除nil结构体按整体零值判定替代关系WithoutEmpty已废弃新代码一律使用Compact更复杂的过滤需求请选用Filter/Reject/Without。在 slice.go 与 slice_test.go 中可以看到完整实现与测试覆盖官方在线示例可通过文档 frontmatter 中的playUrlgo.dev/play/p/tXiy-iK6PAc直接运行体验。【免费下载链接】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),仅供参考