我有一个方法返回需要排序的String列表。但是,我遇到了旧的字符串编号排序问题,并想知道是否有人可以协助比较器实现或指向我的方向。
该列表将返回一个列表:
State Lower Legislative District 1
State Lower Legislative District 11
State Lower Legislative District 12
...
State Lower Legislative District 2
...
State Lower Legislative District 100
...
State Upper Legislative District 1
State Upper Legislative District 11
...
所以,首先我需要做一个基本的String排序,但是我需要按数字排序。要排序的数字应该总是跟踪,可能是2或3位数。
(编辑)我最初的想法是在空格上拆分字符串,在数字部分运行StringUtils.isNumeric,然后排序。但是,这对我来说似乎有点麻烦。
有人可以帮忙吗?
答案 0 :(得分:6)
Coding Horror上有an article about this。这称为natural sorting,您可以将一组数字有效地视为单个“字符”。有关该想法的一些Java实现,请参阅this question。
为人类排序:自然排序
几乎所有编程语言中的默认排序功能都不适合人类消费。那是什么意思?好吧,考虑在Windows资源管理器中排序文件名,并通过
Array.Sort()
代码对这些文件名进行排序之间的区别:
答案 1 :(得分:3)
我在String.CompareTo上写了一个变体,它比较了两个字符串中找到的数字的长度。当装入两个相同长度的数字时,字母数字比较恢复正常。它也会跳过前导零。
public static int compareNatural(String a, String b) {
int la = a.length();
int lb = b.length();
int ka = 0;
int kb = 0;
while (true) {
if (ka == la)
return kb == lb ? 0 : -1;
if (kb == lb)
return 1;
if (a.charAt(ka) >= '0' && a.charAt(ka) <= '9' && b.charAt(kb) >= '0' && b.charAt(kb) <= '9') {
int na = 0;
int nb = 0;
while (ka < la && a.charAt(ka) == '0')
ka++;
while (ka + na < la && a.charAt(ka + na) >= '0' && a.charAt(ka + na) <= '9')
na++;
while (kb < lb && b.charAt(kb) == '0')
kb++;
while (kb + nb < lb && b.charAt(kb + nb) >= '0' && b.charAt(kb + nb) <= '9')
nb++;
if (na > nb)
return 1;
if (nb > na)
return -1;
if (ka == la)
return kb == lb ? 0 : -1;
if (kb == lb)
return 1;
}
if (a.charAt(ka) != b.charAt(kb))
return a.charAt(ka) - b.charAt(kb);
ka++;
kb++;
}
}
答案 2 :(得分:2)
一种方法是使用简单的正则表达式来解析比较器中感兴趣的字段,然后手动比较它们。这是一个未经测试的例子:
private static final Pattern pattern = Pattern.compile("^State (Lower|Upper) Legislative District (\\d+)$");
public int compare(String a, String b) {
Matcher matcher1 = pattern.matcher(a);
Matcher matcher2 = pattern.matcher(b);
if( matcher1.matches() && matcher2.matches() ) {
//compare upper/lower
int upperLowerComparison = matcher1.group(1).compareTo(matcher2.group(1));
if ( upperLowerComparison != 0 ) {
return upperLowerComparison;
}
//number comparison
return Integer.valueOf(matcher1.group(2)).compareTo(Integer.valueOf(matcher2.group(2));
}
//...what to do if they don't match?
}
答案 3 :(得分:1)
您有两种选择。第一个是创建一个具有两个字段的类 - 名称和数字。当然先解析名称和数字。然后在比较器中首先比较名称然后比较数字。第二个是在compare
方法中进行解析。选择哪一个更适合你。
答案 4 :(得分:1)
看看这个实现:
public static int naturalCompare(String a, String b, boolean ignoreCase) {
if (ignoreCase) {
a = a.toLowerCase();
b = b.toLowerCase();
}
int aLength = a.length();
int bLength = b.length();
int minSize = Math.min(aLength, bLength);
char aChar, bChar;
boolean aNumber, bNumber;
boolean asNumeric = false;
int lastNumericCompare = 0;
for (int i = 0; i < minSize; i++) {
aChar = a.charAt(i);
bChar = b.charAt(i);
aNumber = aChar >= '0' && aChar <= '9';
bNumber = bChar >= '0' && bChar <= '9';
if (asNumeric)
if (aNumber && bNumber) {
if (lastNumericCompare == 0)
lastNumericCompare = aChar - bChar;
} else if (aNumber)
return 1;
else if (bNumber)
return -1;
else if (lastNumericCompare == 0) {
if (aChar != bChar)
return aChar - bChar;
asNumeric = false;
} else
return lastNumericCompare;
else if (aNumber && bNumber) {
asNumeric = true;
if (lastNumericCompare == 0)
lastNumericCompare = aChar - bChar;
} else if (aChar != bChar)
return aChar - bChar;
}
if (asNumeric)
if (aLength > bLength && a.charAt(bLength) >= '0' && a.charAt(bLength) <= '9') // as number
return 1; // a has bigger size, thus b is smaller
else if (bLength > aLength && b.charAt(aLength) >= '0' && b.charAt(aLength) <= '9') // as number
return -1; // b has bigger size, thus a is smaller
else
return lastNumericCompare;
else
return aLength - bLength;
}
它应该很快,没有任何正则表达式或数组操作,只有几个标志和很多情况。
这应该对字符串中的任何数字组合进行排序,并正确支持相等的数字并继续前进。
答案 5 :(得分:0)
一个简单的实现就像这个(这适用于任何以数字结尾的字符串):
public class SplitComparator implements Comparator<String> {
static class Pair implements Comparable<Pair> {
private String name;
private Integer number;
public Pair(String value) {
value = value.trim();
this.name = value.substring( 0, value.lastIndexOf(" ") );
this.number = Integer.valueOf( value.substring( value.lastIndexOf(" ") + 1, value.length() ) );
}
@Override
public int compareTo( Pair right) {
int result = this.name.compareTo( right.name );
if ( result == 0 ) {
result = this.number.compareTo( right.number );
}
return result;
}
}
@Override
public int compare(String left, String right) {
return new Pair( left ).compareTo( new Pair( right ) );
}
public static void main( String ... args ) {
String[] values = { "State Lower Legislative District 1",
"State Lower Legislative District 11",
"State Upper Legislative District 1",
"State Upper Legislative District 11"};
SplitComparator comparator = new SplitComparator();
System.out.println( comparator.compare( values[1] , values[0]) );
System.out.println( comparator.compare( values[0] , values[1]) );
System.out.println( comparator.compare( values[0] , values[3]) );
}
}
答案 6 :(得分:-1)
我通常通过在数字前加零并将整个实体作为字符串处理来完成此操作。然后排序。
见:
public abstract class MyNumberComparator {
protected int doCompare(final String number1, final String number2) {
String strNumber1 = fillUpLeftWithZeros(number1, 30);
String strNumber2 = fillUpLeftWithZeros(number2, 30);
return strNumber1.toUpperCase().compareTo(strNumber2.toUpperCase());
}
}