lo 库 Range 系列函数完全指南:从 Range 到 RangeFrom、RangeWithSteps 生成数值序列
2026/9/13 17:17:36 网站建设 项目流程

lo 库 Range 系列函数完全指南:从 Range 到 RangeFrom、RangeWithSteps 生成数值序列

【免费下载链接】lo💥 A Lodash-style Go library based on Go 1.18+ Generics (map, filter, contains, find...)项目地址: https://gitcode.com/GitHub_Trending/lo/lo

Range是 lo(一个基于 Go 1.18+ 泛型的 Lodash 风格 Go 库)中用于生成整数序列的核心数学工具函数,与RangeFromRangeWithSteps共同构成完整的数值序列生成能力,广泛用于切片初始化、循环计数、分页索引构造等场景。本文将结合源码实现与测试用例,深入讲解Range的正向/负向序列语义、底层分配机制,并扩展覆盖RangeFromRangeWithSteps的泛型用法,帮助读者在实际项目中正确、高效地生成任意数值序列。

Range:从 0 开始的定长整数序列

lo.Range是 lo 数学工具集(docs/docs/core/math.md)中的基础函数,其函数签名定义于 docs/data/core-range.md:

func Range(elementNum int) []int

它创建一个指定长度的整数切片,从 0 开始递增;当传入负数长度时,生成递减序列:

lo.Range(4) // []int{0, 1, 2, 3} lo.Range(-4) // []int{0, -1, -2, -3}

实现原理:符号决定步长

Range的实现在仓库根目录的 math.go 中,代码非常精简:

// Range creates a slice of numbers (positive and/or negative) with given length. func Range(elementNum int) []int { step := Ternary(elementNum < 0, -1, 1) length := elementNum * step result := make([]int, length) for i, j := 0, 0; i < length; i, j = i+1, j+step { result[i] = j } return result }

其核心逻辑可以拆解为三步:

  1. 确定步长:通过Ternary(elementNum < 0, -1, 1)判断元素个数的正负。elementNum为正时步长为+1,为负时步长为-1Ternary本身也是 lo 提供的条件表达式辅助函数(实现于 condition.go)。
  2. 计算长度length := elementNum * step将负数取绝对值,得到切片实际容量。例如Range(-4)length = (-4) × (-1) = 4
  3. 填充序列:使用for i, j := 0, 0; ...双变量循环,i跟踪索引、j跟随步长累加,一次性完成切片填充,避免了额外的 append 扩容开销。

边界情况:零长度的行为

elementNum为 0 时,length为 0,make([]int, 0)会返回一个空切片。这在测试用例 math_test.go 中有明确验证:测试表包含三组用例——正数(4{0,1,2,3})、负数(-4{0,-1,-2,-3})以及零(0→ 空切片nil),零长度用例通过is.Empty(result)断言通过。

从测试结构看,Range及其系列函数在仓库中被视为一等公民:每个函数都有对应的独立测试函数(TestRangeTestRangeFromTestRangeWithSteps),且全部使用t.Parallel()并发执行,保证并发安全语义。

使用场景

Range最典型的应用是构造固定长度的索引切片,例如分页组件中的页号序列、初始化棋盘/矩阵的下标集合,或配合lo.Map生成占位数据:

// 生成 5 页的分页索引 [0,1,2,3,4] pages := lo.Range(5) // 配合 Map 生成业务数据 rows := lo.Map(lo.Range(10), func(i int, _ int) string { return fmt.Sprintf("row-%d", i) })

负长度生成递减序列的特性,可以用于倒计时或反向遍历辅助:

countdown := lo.Range(-3) // []int{0, -1, -2}

RangeFrom:指定起始值的定长序列

当需要从非零起始值生成定长序列时,使用RangeFrom。其签名定义于 docs/data/core-rangefrom.md:

func RangeFromT constraints.Integer | constraints.Float []T

它创建一个从start开始、长度由elementNum决定的切片,负长度同样产生递减序列:

lo.RangeFrom(1, 5) // []int{1, 2, 3, 4, 5}

泛型支持:整数与浮点通用

RangeFrom是泛型函数,类型参数T被约束为constraints.Integer | constraints.Float,该约束定义在 internal/constraints/constraints.go,覆盖intint8int64uintfloat32float64等所有内置数值类型。实现位于 math.go:

func RangeFromT constraints.Integer | constraints.Float []T { step := Ternary(elementNum < 0, -1, 1) length := elementNum * step result := make([]T, length) for i, j := 0, start; i < length; i, j = i+1, j+T(step) { result[i] = j } return result }

Range的差异仅在两点:初始值jstart出发,以及步长通过T(step)显式转换为目标类型T。这保证了浮点类型(如float64)也能正确累加。

测试 math_test.go 同时验证了intfloat64两种类型:RangeFrom(1, 5)得到升序序列,RangeFrom(-1, -5)得到降序序列{-1,-2,-3,-4,-5}RangeFrom(10, 0)返回空切片。

RangeWithSteps:指定起点、终点与步长的序列

当需要精确控制步长(而非固定 ±1)时,使用RangeWithSteps。其签名定义于 docs/data/core-rangewithsteps.md:

func RangeWithStepsT constraints.Integer | constraints.Float []T

它创建一个从start开始、到end结束(不包含end)、按step递增或递减的切片:

lo.RangeWithSteps(0, 20, 5) // []int{0, 5, 10, 15}

方向匹配与空结果规则

RangeWithSteps的行为规则比前两者更丰富,实现在 math.go:

  1. start == endstep == 0:直接返回空切片[]T{}。步长为 0 会导致无限循环,因此被显式拦截。
  2. 升序场景(start < end:若step < 0(方向不匹配),返回空切片;否则从start循环累加step直到不小于end
  3. 降序场景(start > end:若step > 0(方向不匹配),返回空切片;否则从start循环累减直到不大于end

因此以下调用都会返回空切片:

lo.RangeWithSteps(0, 5, 0) // step 为 0 lo.RangeWithSteps(0, 5, -1) // 升序却给负步长 lo.RangeWithSteps(5, 0, 1) // 降序却给正步长

而合法降序用法为:

lo.RangeWithSteps(10, 0, -2) // []int{10, 8, 6, 4, 2}

预分配容量的巧妙处理

值得注意的实现细节是 math.go 中的容量预分配逻辑:

capacity := func(count, delta T) int { // Use math.Ceil instead of (count-1)/delta+1 because integer division // fails for floats (e.g., 5.5/2.5=2.2 → ceil=3, not 2). return int(math.Ceil(float64(count) / float64(delta))) }

代码注释明确解释了选择math.Ceil而非(count-1)/delta+1的原因:后者的整数除法在浮点场景下会出错(例如5.5/2.5=2.2,向下取整得到 2,而实际需要 3 个元素)。这个细节体现了该函数对浮点类型支持的严谨性,也提示读者:T为浮点类型时,RangeWithSteps返回的元素个数可能与直觉略有出入,建议在关键业务中先验证结果长度。

it 包变体:Range 的惰性序列版本

仓库还提供基于 Go 1.23iter.Seq的迭代器版本(it/math.go),位于it包,文件名含//go:build go1.23构建标签,仅在 Go 1.23+ 环境下生效:

// Range creates a sequence of numbers (positive and/or negative) with given length. func Range(elementNum int) iter.Seq[int] { step := lo.Ternary(elementNum < 0, -1, 1) length := elementNum * step return func(yield func(int) bool) { for i, j := 0, 0; i < length; i, j = i+1, j+step { if !yield(j) { return } } } }

该版本与切片版共享相同的步长/长度计算逻辑(对应文档 docs/data/it-range.md),区别在于:

  • 惰性求值:序列只在被消费时按需生成,适合构造大范围序列但只取前几个元素的场景;
  • 提前终止yield返回false时立即停止迭代,天然支持break语义;
  • 不产生堆分配:无需make切片,内存开销更小。

it包同样提供RangeFromRangeWithSteps的迭代器版本(分别见 docs/data/it-rangefrom.md 与 docs/data/it-rangewithsteps.md),方向匹配规则与切片版完全一致。

性能特征与基准测试

仓库的基准测试 benchmark/core_math_bench_test.go 为三个函数分别编写了BenchmarkRangeBenchmarkRangeFromBenchmarkRangeWithSteps,均以lengths中的多组数据规模压测。从实现结构看,RangeRangeFrom使用make一次性预分配(一次分配、零扩容),而RangeWithSteps也通过capacity闭包精确预分配,因此三个函数在正常参数下都是单次分配的最优路径。

基准测试还按数据规模分b.Run(strconv.Itoa(n))子测试,便于观察不同长度下的分配与填充耗时。需要说明的是:本文不引用具体基准数据,读者如需对比可自行在本地运行go test -bench=BenchmarkRange -benchmem(基准测试目录位于 benchmark/)。

相关辅助函数

Range系列在文档体系中与以下函数互为关联(见 docs/data/core-range.md 的similarHelpers字段):

  • lo.Times:重复调用回调指定次数并收集结果,可视为「生成序列 + Map」的合成操作;
  • lo.Repeat/lo.RepeatBy:按次数重复元素或按回调重复生成(docs/data/core-repeat.md、docs/data/core-repeatby.md)。

实际编码时可按需组合:例如先用lo.Range生成索引序列,再叠加lo.Maplo.Filter等函数完成流水线式处理。

快速上手

在 Go 1.18+ 项目中安装 lo 并导入即可使用(安装命令见 README.md):

go get github.com/samber/lo@v1
import "github.com/samber/lo" // 升序定长序列 lo.Range(4) // []int{0, 1, 2, 3} // 降序定长序列 lo.Range(-4) // []int{0, -1, -2, -3} // 指定起点的定长序列(支持整数与浮点) lo.RangeFrom(1, 5) // []int{1, 2, 3, 4, 5} // 指定起点、终点与步长(不含终点) lo.RangeWithSteps(0, 20, 5) // []int{0, 5, 10, 15}

若项目已升级至 Go 1.23,还可使用it包的惰性序列版本处理大范围数据,兼顾内存效率与中断语义。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询