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() код:

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

}