【问题标题】:Java String Number ComparatorJava 字符串数字比较器
【发布时间】:2011-11-08 09:14:48
【问题描述】:

我有一个方法返回需要排序的字符串列表。但是,我遇到了旧的字符串数字排序问题,想知道是否有人可以协助 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,然后排序。不过,这对我来说似乎有点杂乱无章。

有人可以帮忙吗?

【问题讨论】:

标签: java natural-sort


【解决方案1】:

编码恐怖中有an article about this。这称为natural sorting,您可以有效地将一组数字视为单个“字符”。请参阅this question 了解该想法的一些 Java 实现。

人类排序:自然排序顺序

几乎所有编程语言中的默认排序函数都不适合人类使用。我的意思是什么?好吧,考虑一下在 Windows 资源管理器中对文件名进行排序和通过 Array.Sort() 代码对那些完全相同的文件名进行排序之间的区别:

continued...

【讨论】:

    【解决方案2】:

    我写了一个关于 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++;
        }
    }
    

    【讨论】:

      【解决方案3】:

      一种方法是使用简单的正则表达式来解析比较器中感兴趣的字段,然后手动比较它们。这是一个未经测试的示例:

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

      【讨论】:

      • 要查找号码,您可以使用lastIndexOf 找到最后一个空格,然后使用substring(lastIndex + 1). 我认为正则表达式在这里是多余的。
      • @Petar:我使用了正则表达式,因为 OP 可能对他的示例进行了一些清理。一旦你有一个稍微复杂一点的例子,你可能需要正则表达式或一个成熟的解析器。这是显示如何提取感兴趣的字段的更一般的答案,但正如您所说,对于这个 exact 数据来说,它也是矫枉过正的。
      【解决方案4】:

      你有两个选择。第一个是创建一个具有两个字段的类 - 名称和编号。当然首先要解析名称和数字。然后在比较器中首先比较名称,然后比较数字。第二种是在compare 方法中进行就地解析。选择哪个更适合您。

      【讨论】:

        【解决方案5】:

        看看这个实现:

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

        它应该很快,没有任何正则表达式或数组操作,只有几个标志和很多情况。

        这应该对字符串中的任何数字组合进行排序,并正确支持相等的数字并继续前进。

        【讨论】:

        • 它说“325”等于“325_0”而不是
        【解决方案6】:

        我通常通过在数字前加上零并将整个实体作为字符串处理来做到这一点。然后排序。

        看这个:

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

        【讨论】:

        • 什么是'fillUpLeftWithZeros'?你能告诉我们它的代码吗?
        【解决方案7】:

        一个简单的实现是这样的(这适用于任何以数字结尾的字符串):

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

        【讨论】:

          猜你喜欢
          • 2017-04-26
          • 2014-11-11
          • 2012-06-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多