【问题标题】:Sorting Strings that contain integers with comparator使用比较器对包含整数的字符串进行排序
【发布时间】:2016-08-18 15:47:34
【问题描述】:

我有一个比较器,它对包含字母和数字的字符串数组进行排序,但似乎无法识别以我正在寻找的方式对它们进行排序的正则表达式。

我使用this question 作为比较器的参考。

array={string-a01,string-a20,string-a100,string-b01,string-b20,string-b100,string-c01,string-c20,string-c100 etc.}

Collections.sort(array, new Comparator<String>(){       
    public int compare(String o1, String o2) {
        return extractInt(o1) - extractInt(o2);
    }

    int extractInt(String s) {
        String num = s.replaceAll("\\D", "");
        return num.isEmpty() ? 0 : Integer.parseInt(num);
    }
});
        
for (String element : array) {
    System.out.println(element);
}

在引入比较器之前,输出为:
string-a01, string-a100, string-a20, string-b01, string-b100, string-b20, string-c01, string-c20, string-c100

这段代码产生的输出是:
string-a01, string-b01, string-c01 string-a20, string-b20, string-c20 string-a100, string-b100, string-c100

我希望它产生的输出是:
string-a01, string-a20, string-a100, string-b01, string-b20, string-b100, string-c01, string-c20, string-c100


编辑:为澄清而编辑。在添加比较器之前,数组已更改并输出。

【问题讨论】:

    标签: java regex sorting comparator


    【解决方案1】:

    假设string 部分实际上不仅仅是"string"。您可以提取结尾的字母部分和数字部分,并使用复合比较器进行比较:

    String[] array = { "string-a20", "string-a01", "string-b01",
        "string-b20", "string-c01", "string-c20",
        "string-a100", "string-b100", "string-c100" };
    
    Pattern p = Pattern.compile("^.*?-([A-Za-z]+)(\\d+)$");
    
    List<String> result = Arrays.stream(array)
        .map(p::matcher)
        .filter(Matcher::find)
        .sorted(Comparator.comparing((Matcher m) -> m.group(1)) // Compare the letter part
            .thenComparingInt(m -> Integer.parseInt(m.group(2)))) // Compare the number part
        .map(m -> m.group(0)) // Map back to String
        .collect(Collectors.toList());
    
    System.out.println(result);
    

    输出:

    [string-a01, string-a20, string-a100, string-b01, string-b20, string-b100, string-c01, string-c20, string-c100]
    

    旧版(缺点是必须重新创建 Matchers):

    Arrays.sort(array, new Comparator<String>() {
    
        Pattern p = Pattern.compile("^.*?-([A-Za-z]+)(\\d+)$");
    
        @Override
        public int compare(String o1, String o2) {
            Matcher m1 = p.matcher(o1);
            Matcher m2 = p.matcher(o2);
    
            if(!(m1.find() && m2.find()))
                return 0; // Or throw a format exception
    
            int comparison = m1.group(1).compareTo(m2.group(1));
            return comparison != 0
                ? comparison 
                : Integer.compare(Integer.parseInt(m1.group(2)), Integer.parseInt(m2.group(2)));
        }
    
    });
    

    【讨论】:

    • 我已经进行了另一次更新,因为最初的问题并没有要求我寻找所有内容。问题是它的顺序是这样的:b01、b100、b11、c01、c100、c11... 数组是动态生成的,没有意识到发布的原始数组不会出现问题
    • @Jon,是的,我正在研究这个,但它有点复杂。
    • @Jon [PLUS ONE] 这一定是答案,因为完美地使用 lambdas 以最简单的方式阐述答案
    • @Jon 很遗憾,我也添加了旧版本。
    • @JornVernee 工作就像一个魅力!对于任何有同样问题的人,不要忘记private final Pattern p = Pattern.compile("^.*?-([A-Za-z]+)(\\d+)$"); 谢谢!!
    【解决方案2】:

    您正在删除 extractInt 方法中的字母字符,因此您将无法在比较中使用它们。

    您应该只使用 no Comparator 对它们进行排序,这将使用默认的字典排序算法对它们进行排序(java.lang.String 实现 Comparable&lt;String&gt;)。

    示例

    // test array
    String[] s = {"string-a01","string-a01","string-b01","string-b02","string-c02","string-c02"};
    
    // sorting with null Comparator, will sort if the type implements Comparable - 
    // which String does
    Arrays.sort(s);
    
    // printing in human-readable form
    System.out.println(
        Arrays.toString(s)
    );
    

    输出

    [string-a01, string-a01, string-b01, string-b02, string-c02, string-c02]
    

    备注

    • 如果您想删除重复项(这可能是您对问题的意图 - 不清楚),请将数组元素添加到 TreeSet 中:

      Set<String> deduplicated = new TreeSet<>(Arrays.asList(s));
      
    • 如果您的排序算法必须使2 出现在12 之前,那么您需要从元素中提取整数值而不将其删除,并且仅在以下情况下进行比较Strings 的其余部分是相等的。

    【讨论】:

    • 你为什么不使用 lambda,更简单
    • @GingerHead 在这种情况下,使用 Java 8 流 API 比 Arrays.sort 更“容易”吗?
    • 例如Arrays.sort(s, (a, b) -&gt; a.length() - b.length());
    • @GingerHead 不。依靠有据可查的行为就足够了。
    • @Mena 我已经进行了另一次更新,因为最初的问题并没有询问我正在寻找的一切。问题是它的顺序是这样的:b01、b100、b11、c01、c100、c11... 数组是动态生成的,没有意识到发布的原始数组不会出现问题
    【解决方案3】:

    听起来您想对“前导字符串”上的字符串进行排序,即所有数字都可以;如果前导字符串相等,则比较后续数字。

    要将字符串拆分为“字符串”和“整数”部分,您可以首先使用“第一个尾随数字”,即字符串中第一个字符与结尾之间没有非数字的位置字符串:

    int firstTrailingDigit(String s) {
      int i = s.length();
      while (i > 0 && Character.isDigit(s.charAt(i - 1))) {
        --i;
      }
      return i;
    }
    

    然后您可以在比较器中使用它:

    public int compare(String a, String b) {
      int ftdA = firstTrailingDigit(a);
      int ftdB = firstTrailingDigit(b);
    
      // Get the leading strings, and compare.
      String sA = a.substring(0, ftdA);
      String sB = b.substring(0, ftdB);
      int compareStrings = sA.compareTo(sB);
      if (compareStrings != 0) {
        // If they're not equal, return the result of the comparison.
        return compareStrings;
      }
    
      // Get the trailing numbers from the strings, and compare.
      int iA = Integer.parseInt(a.substring(ftdA));
      int iB = Integer.parseInt(b.substring(ftdB));
      return Integer.compare(iA, iB);
    }
    

    Ideone demo

    输入:

    String[] array = {"string-a01","string-a20","string-a100","string-b01","string-b20","string-b100","string-c01","string-c20","string-c100"};
    

    输出:

    [string-a01, string-a20, string-a100, string-b01, string-b20, string-b100, string-c01, string-c20, string-c100]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-14
      相关资源
      最近更新 更多