Java String Number Comparator
У меня есть метод, возвращающий список строк, которые необходимо отсортировать. Тем не менее, я столкнулся со старой проблемой сортировки номера строки и задавался вопросом, Может ли кто-нибудь помочь с реализацией компаратора или указать мне в направлении одного.
список собирается вернуть что-то список это:
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
...
Итак, сначала мне нужно сделать базовую сортировку строк, но затем мне нужно отсортировать по номеру. Число для сортировки должно всегда следовать и может быть 2 или 3 десятичные знаки.
(Edit) моя первоначальная мысль-разделить строку на пространство, запустить StringUtils.isNumeric на части номера, затем сортировать. Тем не менее, мне это кажется немного клуджем.
кто может помочь?
7 ответов
здесь статьи об этом о кодировании ужаса. Это называется естественная сортировка, где вы эффективно обрабатываете группу цифр как один "символ". См.этот вопрос для некоторых реализаций Java идеи.
сортировка для людей: естественный порядок сортировки
функции сортировки по умолчанию почти на каждом языке программирования плохо подходят для потребления человеком. Что я имею в виду? Что ж, рассмотрим разницу между сортировкой имен файлов в проводнике Windows и сортировкой тех же самых имен файлов через
Array.Sort()
код:
Я написал вариацию на 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++;
}
}
один из способов-использовать простое регулярное выражение для анализа интересующих полей в вашем компараторе, а затем сравнить их вручную. Вот непроверенный пример:
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?
}
у вас есть два варианта. Первый-это создать класс, имеющий два поля - Имя и номер. Конечно, сначала разберем имя и цифры. Затем в компараторе сначала сравните имя, а затем число. Второй-сделать разбор на месте в compare
метод. Выберите, какой из них вам больше подходит.
взгляните на эту реализацию:
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;
}
Это должно быть быстро, без каких-либо регулярных выражений или манипуляций массивом, всего несколько флагов и много случаев.
это должно сортировать любую комбинацию чисел внутри строк и правильно поддерживать числа, которые равны и двигаться дальше.
простая реализация будет похожа на эту (это работает с любой строкой, которая заканчивается числом):
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]) );
}
}
обычно я делаю это, добавляя нули к числу и обрабатывая всю сущность как строку. затем отсортировать его.
это:
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());
}
}