多级序号列表实现
编辑时间:20200927
背景
待排序数据是某份工作手册的条目,用户按照xxx.xxx.xxx…的形式输入,将会自动确定这条数据所在层级,并且会要求按照期望结果进行排序,这有些类似于word中的多级目录,但是因为数据的原因,直接对这部分进行排序,得不到期望结果,因为数据库是按照字符来进行排序的,所有我们需要在数据入库之前进行处理,让条目,能够正常排序
待排序数据 | 期望结果 | 实际排序结果 |
---|---|---|
1 | 1 | 1 |
2 | 2 | 2 |
3 | 2.9 | 2.10 |
3.2 | 2.10 | 2.9 |
3.1.1 | 3 | 3 |
3.10.2 | 3.1 | 3.1 |
2.9 | 3.1.1 | 3.1.1 |
2.10 | 3.2 | 3.10.2 |
3.1 | 3.10.2 | 3.2 |
需求
- 用户输入xxx.xxx.xxx形式的数据,自动确认数据层级
- 每一级能够支持三位数字
- 能够满足多级序号(无限级)
- 能快速准确排序,并按照期望结果输出
方案
分析
- 因为数据存在多个【.】所以数据库字段类型,只有设置为字符
- 直接对字符排序,不能达到期望结果
- 以MySQL为例,数据库会严格按照0-9的顺序进行排列,前提是他们都在同一个字符位置
数据拆分分析
数据 | 拆分 | 第一位 | 第二位 | 第三位 | 第四位 | 第五位 | 第六位 |
---|---|---|---|---|---|---|---|
1 | => | 1 | |||||
2 | => | 2 | |||||
3 | => | 3 | |||||
3.2 | => | 3 | . | 2 | |||
3.1.1 | => | 3 | . | 1 | . | 1 | |
3.10.2 | => | 3 | . | 1 | 0 | . | 2 |
2.9 | => | 2 | . | 9 | |||
2.10 | => | 2 | . | 1 | 0 | ||
3.1 | => | 3 | . | 1 |
观察他们拆分后的位置,以及mysql字符排序的原则,不难理解,为什么达不到我们所期望的结果,
因为1一直会排在9前面,所以造成了2.10排在了2.9的前面,后面的数据也是因为这个原因错位了
解决方案
方案一
将每一个部分设计为一个字段,每个字段都为数字类型,然后按顺序进行排序,在显示的时候可以选择将每个字段进行拼接,也可以设计一个字段存放用户输入的数据
缺点
当前方案存在缺点,不能实现无限级的数据解析
方案二(推荐)
在用户输入数据后,对数据进行一遍处理(这里约定每一位最大值为999,所以将每一位的数据处理为000的形式,如果每一位最大值为9999,可以将数据处理为0000,五位,六位以此类推)
用户输入数据 | 处理结果 | |
---|---|---|
1 | => | 001 |
2 | => | 002 |
3 | => | 003 |
3.2 | => | 003002 |
3.1.1 | => | 003001001 |
3.10.2 | => | 003010002 |
2.9 | => | 002009 |
2.10 | => | 002010 |
3.1 | => | 003001 |
对处理后的数据,进行拆分,可以得到如下结果(空数据可以看做为0),排序的时候,数据库会对每一位从0到9依次排序
数据 | 拆分 | 第一位 | 第二位 | 第三位 | 第四位 | 第五位 | 第六位 | 第七位 | 第八位 | 第九位 |
---|---|---|---|---|---|---|---|---|---|---|
001 | => | 0 | 0 | 1 | ||||||
002 | => | 0 | 0 | 2 | ||||||
003 | => | 0 | 0 | 3 | ||||||
003002 | => | 0 | 0 | 3 | 0 | 0 | 2 | |||
003001001 | => | 0 | 0 | 3 | 0 | 0 | 1 | 0 | 0 | 1 |
003010002 | => | 0 | 0 | 3 | 0 | 1 | 0 | 0 | 0 | 2 |
002009 | => | 0 | 0 | 2 | 0 | 0 | 9 | |||
002010 | => | 0 | 0 | 2 | 0 | 1 | 0 | |||
003001 | => | 0 | 0 | 3 | 0 | 0 | 1 |
这样处理过后,对处理后的数据进行排序,就能得到我们想要的输出结果,这样的处理方式,在理论上就能实现无限级的排序,处理后的数据,可以在后面进行一个补0的操作,但是我们不确定到底有多少层级,所有我们不进行补齐,依然可以实现(补齐只是数据看起来好看一些)
实现
实现部分,主要是对用户输入数据,进行格式化,实现方式很多,现在列举一种
/**
* <p>
* 处理序号,对用户输入的序号进行转换
* <pre>
* 1 => 001
* 1.1 => 001001
* 23.2 => 023002
* 3.4.15.3 => 003004015003
* </pre>
* </p>
*
* @param serialNum : 用户输入的序号
* @return {@link String} 处理后的序号 (1.2.13.111)001002013111
* @author wangyihuai
* @date 2020/09/29 下午 03:26
*/
public String parseSerial(String serialNum) {
//格式化
DecimalFormat decimalFormat = new DecimalFormat("000");
//按照【.】将数据进行分割
String[] serials = serialNum.split("\\.");
StringBuilder builder = new StringBuilder();
for (String serial : serials) {
//限定每一个节点只能输入三位
if (serial.length() > 3) {
//TODO 异常处理
} else {
//将格式化后的数据,进行拼接
builder.append(decimalFormat.format(Integer.valueOf(serial)));
}
}
return builder.toString();
}
写在最后
第二种解决方案可以用在很多有多级数据排序的地方,
例:
- 电商经常会用到的面包屑(分类)
- 省市区
- 多级目录
- 部门树
- ……
上诉场景,除了会有pid记录层级关系,还会有一个字段记录当前数据的层级关系(例如:001/002/003),一般有多级树(三级或三级以上)的需求都会用这种方式进行实现
本文中的数据,需要排序,所以没有加分隔符,省市区,部门等需求一般会让用户指定顺序,所以记录层级关系的字段,也只是用来快速定位的