目录
1.前言
字符串比较大小对于任何一个程序员来说并不陌生,对于一个Java程序员,我们知道使用String#compareTo(str)或者是String#compareToIgnoreCase(str)进行比较,后者是忽略大小写。正是因为这是一个很简单的事情,才导致我们不假思索的去实现,然后出现了很可笑却有时候很无奈的事情。比如,这里有两个字符串,分别是【S5】和【S10】,按照我们正常的逻辑来说【S10】应该排在后面,然而当我们去调用String#compareTo(str)方法的时候却事与愿违。而在实际的过程中,还有比这更复杂的字符串,追求根本还是数字给我们惹得祸。那么,笔者就通过两个在工作中遇到的例子聊一下工作中字符串比较的那些事。
2.案例一
2.1 需求描述
现在给定两个字符串,我们姑且认为是元器件的型号规格。我们需要对齐进行比较排序。在比较过程中,如果出现数字,需要根据数字的大小进行排序,而不是字符串排序。比如,现在两个字符串分别为【XDE100RTE】和【XDE15RGF】。我们看到这两个字符串前缀都相等,为【XDE】;但是后面的却发生了区别,第一个字符串数字为【100】,第二个字符串数字为【15】,如果我们单纯的进行比较,那结果会差之千里。所以我们需要对此比较进行特殊处理。
2.2 需求分析
通过上面的示例我们发现,两个字符前面的多个字符串都相等,所以我们可以按照字符串的比较进行,当发现当前位的字符为数字,我们需要将此为左右相邻的字符串都提取到,进行数学比较,进而返回结果。
2.3 代码实现1
public static int compareStr(String str1,String str2){
str1 = str1 == null ? "" : str1;
str2 = str2 == null ? "" : str2;
if (str1.equals(str2)) {
return 0;
}
//获取两个字符串较短长度
int n = Math.min(str1.length(), str2.length());
int result = 0;
int position = 0;
int numPosition = -1;
for (; position < n; position++) {
int int1 = str1.charAt(position);
int int2 = str2.charAt(position);
if(int1 >= 48 && int1 <= 57 && int2 >= 48 && int2 <= 57){
if(numPosition == -1){
numPosition = position;
}
}else{
numPosition = -1;
}
result = int1 - int2;
if(result == 0) {
continue;
}
if(numPosition == -1){
return result;
}
break;
}
String compare1 = str1.substring(numPosition).replaceAll("(\\d+)[^\\d].*","$1");
String compare2 = str2.substring(numPosition).replaceAll("(\\d+)[^\\d].*","$1");
//这里正则表达式保证转换字符串为数字肯定成功,
return Integer.valueOf(compare1) - Integer.valueOf(compare2);
}
2.4 代码实现2
public static int compare2(String s1, String s2) {
s1 = s1 == null ? "" : s1;
s2 = s2 == null ? "" : s2;
if (s1.equals(s2)) {
return 0;
}
if (s1.matches("^\\d.*") ^ s2.matches("^\\d.*")) {
return s1.compareTo(s2);
}
String[] s1Arr = s1.replaceAll("(\\d+)", "``$1``").split("``");
String[] s2Arr = s2.replaceAll("(\\d+)", "``$1``").split("``");
int n = Math.min(s1Arr.length, s2Arr.length);
for (int i = 0; i < n; i++) {
String s1Str = s1Arr[i];
String s2Str = s2Arr[i];
int result = compares(s1Str, s2Str);
if (result != 0) {
return result;
}
}
return s1Arr.length - s2Arr.length;
}
private static int compares(String s1Str, String s2Str) {
if (!s1Str.matches("\\d+") && !s2Str.matches("\\d+")) {
return s1Str.compareTo(s2Str);
}
return Integer.valueOf(s1Str) - Integer.valueOf(s2Str);
}
2.5 小结
其实还是有些小郁闷的,代码1是晚上写出来的,代码2是之前写的。今天晚上还是有一些成就的。还是
3.案例二
3.1 需求描述
现在给定一个字符串,这是一个由N个子串以“,”分割的字符串,其中每个子串是以一个或者多个字符与数字拼接而成,而且每个子串不会有重复。这么说的有些抽象,我们举一个简单的例子。例如,"C3,C1,AB5,C2,AB1,C10,C6,C9,C11,C21,AB3,AB4"。现在需要对其进行如下处理:
1)前缀相同的要放到一起,按照上面的字符串,应该把C开头的放到一起,AB开头的放到一起。
2)对于开头字符相同的按照子串数字部分进行排序,这里需要注意不能将C9排到C10后面。
3)对于子串后缀数字存在三个以及以上连续的排序,要用"~"进行合并。例如上面的字符串,包含【C9,C10,C11】,所以应该写成C9~C11。
好了,需要说完了。是否感觉有些晕。那么我们现在就开始分析了。
3.2 需求分析
1)首先我们检查给定的字符串是否满足我们的要求,如果不满足就直接退出。
2)为了方便处理,需要定义一个对象TagNum,对象记录每个子串的前缀和后缀编号。
3)定义TagNum的相邻规则,前缀相同,后缀差1定义为相邻,其他情况定义为不相邻。
4)计算当前值和后一个值的相邻关系,并记录到当前值
5)拼接字符串
这里涉及到一个逻辑,我们可以捋一下。我们先从第二个元素开始,和前面的相邻关系进行比较。
所以我们有逻辑:
1)当前一个为0(false),应该追加【,当前值】;
2)当前一个值为1(true),当前值为1,跳过本次循环;
3)当前一个值为1(true),当前值为0,应该追加至【~当前值】
3.3 代码实现
public class StringUtil {
public String changeOccur(String str){
if(null == str || "".equals(str)){
return "";
}
//验证字符串是否满足规则
String reg = "([A-Za-z]+\\d+,)*[A-Za-z]+\\d+";
if(!str.matches(reg)){
String msg = String.format("传入字符%s串不符合要求", str);
throw new RuntimeException(msg);
}
String[] occuArr = str.split(",");
//转换成数组
Set<TagNum> tagSet = new TreeSet();
for (int i = 0; i < occuArr.length; i++) {
tagSet.add(new TagNum(occuArr[i].trim()));
}
List<TagNum> tagList = new ArrayList<>(tagSet);
List<Boolean> tagBool = new ArrayList<>();
for (int i = 0; i < tagList.size()-1; i++) {
tagBool.add(tagList.get(i).adjoin(tagList.get(i+1)));
}
tagBool.add(false);
StringBuilder resultSb = new StringBuilder();
resultSb.append(tagList.get(0));
for (int i = 1; i < tagBool.size(); i++) {
if(!tagBool.get(i-1)){
resultSb.append(",").append(tagList.get(i));
continue;
}
if(!tagBool.get(i)){
resultSb.append("~").append(tagList.get(i));
}
}
return resultSb.toString();
}
private class TagNum implements Comparable<TagNum>{
private String pre;
private Integer num;
private TagNum(String tagNum){
String pre = tagNum.replaceAll("([A-Za-z]+)\\d+","$1");
String numStr = tagNum.substring(pre.length());
this.pre = pre;
this.num = Integer.valueOf(numStr);
}
public boolean adjoin(TagNum otherTagNum){
if(pre.equalsIgnoreCase(otherTagNum.getPre())){
return otherTagNum.getNum()- num == 1;
}else{
return false;
}
}
public String getPre() {
return pre;
}
public void setPre(String pre) {
this.pre = pre;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
@Override
public int compareTo(TagNum o) {
int result = getPre().compareToIgnoreCase(o.getPre());
if(result != 0){
return result;
}
return getNum() - o.getNum();
}
@Override
public String toString() {
return pre + num;
}
}
}