我正在参加「码上掘金迎新年」编程比赛,详情请看:码上掘金迎新年
耐心和持久胜过激烈和狂热。
哈喽大家好,我是陈明勇,本文介绍的内容是有规律的数字版本号的排序工具的创作解读。如果本文对你有帮助,不妨点个赞,如果你是 Go 语言初学者,不妨点个关注,一起成长一起进步,如果本文有错误的地方,欢迎指出!
前言
工具源码(码上掘金源码地址):code.juejin.cn/pen/7185889…
在某些场景,我们可能需要对版本号进行排序。版本号的形式有很多种,例如:
- 1.0.0, 1.0.1.1, 2.0.1.1
- v1.0.0, v1.10.1, v2.0
- ······
而本文所介绍的版本号排序工具,是针对有规律的数字版本号如 1.0.0, 1, 2.15.0 这种形式。
创作解读
版本号的大小比较与排序
版本号排序的前提,首先得比较两个版本号的大小。由于版本号长度可能不一致,所以需要额外做一些处理。对于版本号的比较,我的算法思路是:
- 1、以
.为分隔符,将版本号的每段数字存到切片里,方便后续比较大小。例如"1.0"→["1", "0"],"1.0.1"→["1", "0", "1"]。firstVersions := strings.Split(versions[i], ".") secondVersions := strings.Split(versions[j], ".") - 2、由于两个版本号的长度可能不一致,因此需要做
填充0,统一长度的操作。所以第二步就是获取两个版本号中,最大长度,然后对长度最小的版本号切片,填充零,保持两个版本号的长度一致。例如第一步的两个版本号["1", "0"]、["1", "0", "1"],需要对第一个版本号填充一个零(填充之后的结果 →["1", "0", "0"]),才能保持两个版本号的长度一致,方便后续比较。// 0 填充 // 获取最大长度并向最小长度的切片填充 "0",统一长度 func getMaxAndFillZero(s1 *[]string, s2 *[]string) int { len1, len2 := len(*s1), len(*s2) if len1 > len2 { fillZero(s2, len1-len2) return len1 } fillZero(s1, len2-len1) return len2 } // 0 填充 func fillZero(s *[]string, size int) { for i := 0; i < size; i++ { *s = append(*s, "0") } }size为最大长度 - 最小长度的值,也就是要填充0的个数。 - 3、遍历切片,从前依次比较两个版本号每段数字的大小。
- 如果第一个版本号的第一段数字大于或小于第二个版本号的第二段数字,则可以根据排序规则决定两个版本号的先后位置。
- 如果相等,则比较下一段数字的大小,以此类推。
for k := 0; k < maxLen; k++ { // 由于上面判断了版本号的合法性,因此 error 可以忽略 vi, _ := strconv.Atoi(firstVersions[k]) vj, _ := strconv.Atoi(secondVersions[k]) if vi < vj { if sortRule == DESC { // 降序排序 // todo 交换操作 } // 默认升序排序,即使 sortRule 不是 ASC // todo 交换操作 } else if vi > vj { // 降序排序 if sortRule == DESC { // todo 交换操作 } // 默认升序排序,即使 sortRule 不是 ASC // todo 交换操作 } }
对字符串切片的排序,本工具使用的函数是 SliceStable(x any, less func(i, j int) bool),通过此函数,可以自定义比较大小的规则。
sort.SliceStable(versions, func(i, j int) bool {
firstVersions := strings.Split(versions[i], ".")
secondVersions := strings.Split(versions[j], ".")
// 判断版本号格式的合法性
isNormal(firstVersions)
isNormal(secondVersions)
// 获取最大值并填充 "0", 统一长度
maxLen := getMaxAndFillZero(&firstVersions, &secondVersions)
for k := 0; k < maxLen; k++ {
// 由于上面判断了版本号的合法性,因此 error 可以忽略
vi, _ := strconv.Atoi(firstVersions[k])
vj, _ := strconv.Atoi(secondVersions[k])
if vi < vj {
if sortRule == DESC {
// 降序排序
return false
}
// 默认升序排序,即使 sortRule 不是 ASC
return true
} else if vi > vj {
// 降序排序
if sortRule == DESC {
return true
}
// 默认升序排序,即使 sortRule 不是 ASC
return false
}
}
return false
})
版本号的合法性校验
由于本工具处理的版本号是有规律的数字版本号,如果版本号包含字母或其他特殊字符,会影响到排序的进行,因此需要提前对版本号进行合法性的校验。
// 判断版本号的格式是否合法
func isNormal(versions []string) {
for _, v := range versions {
for _, r := range []rune(v) {
if !unicode.IsNumber(r) {
panic(errors.New("版本号格式错误:" + string(r)))
}
}
}
}
遍历每段版本号,然后对每段版本号的字符进行遍历,判断是否是数字,如果不是,则 panic 掉,结束排序。
错误处理
由于版本号的不合法性,可能会程序运行的过程中产生错误。因此,有必要人工捕获错误,提高工具的健壮性。
版本号排序函数提供一个 error 的返回值,用于判断是否产生错误。错误的捕获逻辑如下:
defer func() {
if r := recover(); r != nil {
if er, ok := r.(error); ok {
err = er
} else {
err = errors.New("")
fmt.Println("未知错误: ")
fmt.Println(r)
}
}
}()
捕获版本号的合法性校验时主动 panic 的错误,并结束排序。
总结
- 本工具实现了对有规律的数字版本号集合进行排序。
- 在排序的过程中,由于版本号的长度可能不一致,因此执行填充
0操作,统一长度,再进行版本号的大小比较; - 除此之外,还对版本号的合法性做了校验,捕获可预知和不可预知的
panic错误,提高了工具的健壮性。 - 经测试,核心功能已实现,但有些地方还能改进,后续会对代码进行优化。