背景
有一组顺序错乱的字符串如下:
第10节课、第2节课、第1节课、第11节课、第100节课、第99节课、第101节课、第9节课
对字符串进行排序,期望结果肯定是这样的:
第1节课、第2节课、第9节课、第10节课、第11节课、第99节课、第100节课、第101节课
通过字符串对比排序:
private fun test() {
val array1: List<String> = listOf(
"第10节课",
"第2节课",
"第1节课",
"第11节课",
"第100节课",
"第99节课",
"第101节课",
"第9节课",
)
Collections.sort(array1,object:Comparator<String>{
override fun compare(o1: String, o2: String): Int {
return o1.compareTo(o2)
}
})
println(array1)
}
实际结果是这样的:
[第100节课, 第101节课, 第10节课, 第11节课, 第1节课, 第2节课, 第99节课, 第9节课]
比如“第100节课”和“第1节课”这组值,按照字符对比的话,前面相同,而"0"小于"节",自然是“第100节课”排在“第1节课”前面,但是这样的结果并不是我们预期的。
分析
看这组字符串的特点:
- 包含数值
- 数值前面部分是相同的
字符串排序,如果前面的字符都不一致,那么还没到数值部分就已经排好序了,就不需要再对比数值了。前面的字符相同,后面按照我们的预期排序的话,我们就需要将数值提取出来,按照数值大小做对比就可以了
方案
网上找到以下方案:

此方案单纯提取了字符串中的数值,对比数值大小排序的,可以解决部分问题。但是如果我们的字符串是"第2-1-2节"这样的(包含多个数值),这个方案就不能处理了
兼容方案
按照数值将字符串拆分成数组,比如"第5章第100节课"拆分成"[第,5,章第,100,节课]",拆分后依次对比两个数组中的每一个值,如果都是数值,那么按照数值大小做对比,否则按照字符串对比。
可以兼容以下场景:
- 无数值时按照字符串对比
- 字符串中如果包含多组数值,可以正常排序
具体代码
/**
* 包含数字的字符串进行比较(按照从小到大排序)
*/
fun compareString(string1: String?, string2: String?): Int {
//拆分两个字符串
val lstString1 = splitString(string1.orEmpty())
val lstString2 = splitString(string2.orEmpty())
//依次对比拆分出的每个值
var index = 0
while (true) {
//相等表示两个字符串完全相等
if (index >= max(lstString1.size, lstString2.size)) {
return 0
}
val str1 = if (index < lstString1.size) lstString1[index] else ""
val str2 = if (index < lstString2.size) lstString2[index] else ""
//字符串相等则继续判断下一组数据
if (str1 == str2) {
index++
continue
}
//是纯数字,比较数字大小
if (isNum(str1) && isNum(str2)) {
return if (str1.toInt() < str2.toInt()) -1 else 1
}
return if (str1 < str2) -1 else 1
}
}
/**
* 拆分字符串
* 输入:第5章第100节课
* 返回:[第,5,章第,100,节课]
*/
private fun splitString(string: String): List<String> {
val matcher = Pattern.compile("([^0-9]+)|(\\d+)").matcher(string)
val list = mutableListOf<String>()
while (matcher.find()) {
list.add(matcher.group())
}
return list
}
/**
* 是否是纯数字
*/
private fun isNum(string: String): Boolean {
return Pattern.compile("\\d+").matcher(string).matches()
}
案例
案例一:
val array1: List<String> = listOf(
"第10节课",
"第2节课",
"第1节课",
"第11节课",
"第100节课",
"第99节课",
"第101节课",
"第9节课",
)
Collections.sort(array1,object:Comparator<String>{
override fun compare(o1: String, o2: String): Int {
return compareString(o1,o2)
}
})
println(array1)
输出结果:
[第1节课, 第2节课, 第9节课, 第10节课, 第11节课, 第99节课, 第100节课, 第101节课]
案例二:
val array2: List<String> = listOf(
"第1-1-2节",
"第2-1-2节",
"第2-2-3节",
"第2-10-2节",
"第10-2-3节",
)
Collections.sort(array2, object : Comparator<String> {
override fun compare(o1: String, o2: String): Int {
return compareString(o1, o2)
}
})
println(array2)
输出结果:
[第1-1-2节, 第2-1-2节, 第2-2-3节, 第2-10-2节, 第10-2-3节]
案例三:
val array3: List<String> = listOf(
"第1节课",
"第10节课",
"第9节课",
"无数值",
"第1-1-2节",
"第2-1-2节",
"第2-2-3节",
"第2-10-2节",
"第10-2-3节",
"无数值二",
"1数值在前",
"10数值在前",
)
Collections.sort(array3, object : Comparator<String> {
override fun compare(o1: String, o2: String): Int {
return compareString(o1, o2)
}
})
println(array3)
输出结果:
[1数值在前, 10数值在前, 无数值, 无数值二, 第1-1-2节, 第1节课, 第2-1-2节, 第2-2-3节, 第2-10-2节, 第9节课, 第10-2-3节, 第10节课]
注:IOS中找到以下排序方案
在iOS中对包含字母数字单词的数组进行排序https://qa.1r1g.com/sf/ask/1453093981/兼容方案的排序结果就是对其的IOS官方API的这个排序结果。
Java中如果还有其他比较好的方案,欢迎指正...