Компаратор строк Java - PullRequest
       57

Компаратор строк Java

8 голосов
/ 01 сентября 2011

У меня есть метод, возвращающий список строк, которые должны быть отсортированы. Тем не менее, я сталкиваюсь со старой проблемой сортировки чисел String, и мне было интересно, может ли кто-нибудь помочь с реализацией 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 цифры.

(Правка) Моя первоначальная мысль - разделить строку на пробел, запустить StringUtils.isNumeric в числовой части, а затем отсортировать. Тем не менее, мне это кажется чем-то вроде клочья.

Кто-нибудь может помочь?

Ответы [ 7 ]

6 голосов
/ 01 сентября 2011

Есть статья об этом на Coding Horror. Это называется естественная сортировка , где вы эффективно обрабатываете группу цифр как один «символ». См. этот вопрос для некоторых реализаций идеи Java.

Сортировка для людей: естественный порядок сортировки

Функции сортировки по умолчанию практически на всех языках программирования плохо подходят для потребления человеком. Что я имею в виду под этим? Хорошо, рассмотрим разницу между сортировкой имен файлов в проводнике Windows и сортировкой тех же имен файлов с помощью Array.Sort() кода:

Windows Explorer Array.sort()

продолжение ...

3 голосов
/ 28 февраля 2014

Я написал вариант для 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 голосов
/ 01 сентября 2011

Один из способов - использовать простое регулярное выражение для анализа областей интереса в компараторе, а затем сравнить их вручную. Вот непроверенный пример:

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?
}
1 голос
/ 12 ноября 2014

Посмотрите на эту реализацию:

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;
}

Это должно быть быстро, без каких-либо регулярных выражений или манипуляций с массивами, только пара флагов и много случаев.

Это должно сортировать любую комбинацию чисел внутри строк и правильно поддерживать числа, которые равны и двигаться дальше.

1 голос
/ 01 сентября 2011

У вас есть два варианта. Первый - создать класс, имеющий два поля - имя и номер. Конечно сначала разбери имя и цифры. Затем в компараторе сначала сравните имя, а затем номер. Второй - выполнить синтаксический анализ на месте в методе compare. Выберите, какой из них вам больше подходит.

0 голосов
/ 01 сентября 2011

Простая реализация будет похожа на эту (это работает с любой строкой, заканчивающейся числом):

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]) );

}

}
0 голосов
/ 01 сентября 2011

Обычно я делаю это, добавляя нули к числу и обрабатывая всю сущность как строку. тогда сортируй это.

Смотрите это:

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());    
   }

}
...