Go语言实现有规律的数字版本号的排序工具

   2023-02-07 学习力0
核心提示:目录前言创作解读版本号的大小比较与排序版本号的合法性校验错误处理总结前言在某些场景,我们可能需要对版本号进行排序。版本号的形式有很多种,例如:1.0.0, 1.0.1.1, 2.0.1.1v1.0.0, v1.10.1, v2.0······而本文所介绍的版本号排序工具,是针对有规律

前言

在某些场景,我们可能需要对版本号进行排序。版本号的形式有很多种,例如:

  • 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 错误,提高了工具的健壮性。
  • 经测试,核心功能已实现,但有些地方还能改进,后续会对代码进行优化。
原文地址:https://juejin.cn/post/7186676435912753210
 
标签: Go语言 排序 工具
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • Go语言使用goroutine及通道实现并发详解
    Go语言使用goroutine及通道实现并发详解
    目录使用通道接收数据阻塞接收数据非阻塞接收数据接收任意数据,忽略掉接收的数据循环接收数据使用通道接收数据在上一篇文章中介绍了通道以及使用通道发送数据,本篇接着了解通道的基本内容,如何使用通道接收数据;通道的接收同样使用"-"操作符;使用通道接
  • Go语言数据结构之希尔排序示例详解 go语言堆排序
    Go语言数据结构之希尔排序示例详解 go语言堆排
    目录希尔排序算法思想图解算法Go 代码实现:总结希尔排序在插入排序中,在待排序序列的记录个数比较少,而且基本有序,则排序的效率较高。1959 年,Donald Shell 从“减少记录个数” 和 “基本有序” 两个方面对直接插入排序进行了改进,提出了希尔排序
  • go语言 nil使用避坑指南
    目录引言nil默认值nil (重点记住)nil没有默认类型不同类型的nil值占用的内存大小可能是不一样的不同类型 nil 的指针是一样的不同类型的 nil 是不能比较的引言今天笔试题遇到 var x string = nil ,问这个定义是否正确?这里给出答案:cannot use nil as strin
    02-09 gonil避坑
  • Go语言开发保证并发安全实例详解 go语言处理高
    目录什么是并发安全?Mutex悲观锁乐观锁版本号机制CAS互斥锁读写互斥锁什么是并发安全?在高并发场景下,进程、线程(协程)可能会发生资源竞争,导致数据脏读、脏写、死锁等问题,为了避免此类问题的发生,就有了并发安全。这里举一个简单的例子: var data
  • Go语言包和包管理详解 go 常用包
    目录1 包简介1.1 工作空间1.2 源文件1.3 包命名1.4 main 包2导包2.1 两种方式2.2 包的别名2.3 简洁模式2.4非导入模式(匿名导入)2.5 导包的路径2.6 远程导入3 初始化 init3.1 init总结4 包管理4.1 演变过程4.2 Go Model优点4.3 启用go module4.4 GOPROXY5 go m
  • Go语言制作svg格式树形图的示例代码
    Go语言制作svg格式树形图的示例代码
    目录什么是SVGSVG定义SVG优点预定义元素圆形 circle直线 line文字 text结点SVG格式根结点子树结点叶结点结点坐标结点文本二叉树转SVG全部源代码最近一直在刷二叉树题目,但在要验证结果时,通常用中序遍历、层序遍历查看结果,验证起来没有画图来得直观,所有
    02-09 Go树形图
  • go语言int类型最大值 go int长度
    go语言int类型最大值 go int长度
      正数的补码是自己本身负数的补码是,先取反码(首尾不反),然后+1 2.  在Go语言中  ^0表示对0取反  我们假如是4位代表一个数字的话,最高位是符号位  0在计算机中用补码的形式存在是 : 0000  取反得到  :1111  (是-1在计算机中以补码
    02-09
  • go包管理工具glide使用方法 go语言包管理工具
    golang没有官方最佳管理方案,在go的世界里存在大量的自制解决方案。go语言的包是没有中间库统一管理的,通过使用go get命令从远程代码库(github.com,goolge code 等)拉取,直接跳过中间版本库的约束,让代码的拉取直接基于源代码版本控制库开发者间的协同直
    02-09
  • [Go语言]从Docker源码学习Go——指针和Structs
    这两天在看reflect这个包在Docker中的使用时,遇到了各种问题,最后虽然知道怎么用了。但是对于这块的原理还不是太懂,于是把"THE WAY TO GO"中关键的几章看了下。继续坚持往下写,争取能说明白。源码还是先看Docker中源码, docker/api/client/cli.gotype Doc
    02-09
  • thrift简单示例 (go语言)
    这个thrift的简单示例来自于官网 (http://thrift.apache.org/tutorial/go), 因为官方提供的例子简单易懂, 所以没有必要额外考虑新的例子. 关于安装的教程, 可以参考https://www.cnblogs.com/albizzia/p/10838646.html, 关于thrift文件的语法, 可以参考: https
    02-09
点击排行