【问题标题】:How to sort a String collection that contains numbers?如何对包含数字的字符串集合进行排序?
【发布时间】:2013-11-25 01:32:13
【问题描述】:

我有一个包含如下数据的字符串向量:

5:34, 5:38, 17:21, 22:11, ...

如果我尝试使用 Collections.sort( ... ); 合并它,它将如下所示:

17:21、22:11、5:34、5:38

其实我希望它看起来像这样:

5:34、5:38、17:21、22:11

所以我想根据冒号“:”之前的数字对元素进行排序,那么如果某些元素在“:”之前具有相同的数字,那么根据“:”之后的数字对它们进行排序。

最简单的方法是什么?

【问题讨论】:

    标签: java sorting collections


    【解决方案1】:

    正确的方法是不要将非字符串值存储为字符串。

    集合中的数据有一定的结构和规则,不能是任意字符串。因此,您不应使用 String 数据类型。

    让我们定义一个名为TwoNumbers 的类型(因为我不知道该类型应该代表什么,即使我能猜到):

    class TwoNumbers implements Comparable<TwoNumbers> {
        private final int num1;
        private final int num2;
    
        public TwoNumbers(int num1, int num2) {
            if (num1 <= 0 || num2 <= 0) {
                throw new IllegalArgumentException("Numbers must be positive!");
            }
            this.num1 = num1;
            this.num2 = num2;
        }
    
        public static TwoNumbers parse(String s) {
            String[] parts = s.split(":");
            if (parts.length != 2) {
                throw new IllegalArgumentException("String format must be '<num>:<num>'");
            }
            try {
                return new TwoNumbers(Integer.parseInt(parts[0]), Integer.parseInt(parts[0]));
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException("parts must be numeric!", e);
            }
        }
    
        public int getNum1() {
            return num1;
        }
    
        public int getNum2() {
            return num2;
        }
    
        @Override
        public int compareTo(TwoNumbers o) {
            if (o == null) {
                return 1;
            }
            int diff = Integer.compare(o.num1, this.num1);
            if (diff == 0) {
                diff = Integer.compare(o.num2, this.num2);
            }
            return diff;
        }
    }
    

    compareTo 方法作为the Comparable interface 的实现存在:它定义了这种类型的对象的排序方式。

    我使用了final 字段(并且不提供设置器),因为该类实现了immutable objects

    通过这种方式,您可以直接对数据进行排序,而无需额外的Comparator,并且不需要在整个程序中分发所有“拆分和解析”代码。相反,您有一个 single 类来负责处理该特定格式,并且所有其他代码片段都可以使用它。

    【讨论】:

    • @Joachim .. 感谢您的出色解决方案。只有 2 个问题:您为什么使用“最终”字段?什么是 compare(...) 方法?
    • @Brad:我已经用一些细节和一些链接更新了我的答案。
    【解决方案2】:

    这是非常低效的,但它应该可以完成这项工作。

    Collections.sort(data, new Comparator<String>(){
        public int compare(String a, String b){
            String[] as = a.split(":");
            String[] bs = b.split(":");
            int result = Integer.valueOf(as[0]).compareTo(Integer.valueOf(bs[0]));
            if(result==0)
                result = Integer.valueOf(as[1]).compareTo(Integer.valueOf(bs[1]));
            return result;
        }
    })
    

    (提示:如果是我的代码,我会优化它以使用子字符串而不是 String.split(),但我太懒了)

    【讨论】:

      【解决方案3】:

      您可以创建自定义 Comparator 以拆分 String 并将其解析为两个整数,创建一个定制类来表示每个 String 并将其存储在 @987654324 @ 反而。我赞成后一种方法,因为您只会产生一次拆分/解析字符串的开销;例如

      public class Data implements Comparable<Data> {
        private final int prefix;
        private final int suffix;
      
        public Data(String str) {
          String[] arr = str.split(":");
      
          if (arr.length != 2) {
            throw new IllegalArgumentException();
          }
      
          this.prefix = Integer.parseInt(arr[0]);
          this.suffix = Integer.parseInt(arr[1]);
        }
      
        public int compareTo(Data data) {
          // Should really avoid subtraction in case of overflow but done to keep code brief.
          int ret = this.prefix - data.prefix;
      
          if (ret == 0) {
            ret = this.suffix - data.suffix;
          }
      
          return ret;
        }
      
        // TODO: Implement equals and hashCode (equals to be consistent with compareTo).
      
        public String toString() { return String.format("%d:%d", prefix, suffix); }
      }
      

      那么这只是在你的Collection 中存储一些Data 对象的情况;例如

      List<Data> l = new ArrayList<Data>();
      l.add(new Data("13:56"));
      l.add(new Data("100:16"));
      l.add(new Data("9:1"));
      Collections.sort(l);
      

      还有一件事 - 你提到你正在使用Vector。您应该尽量避免使用 Vector / Hashtable,因为它们已被 List / Map 取代,它们是作为 JDK 1.2 中集合框架的一部分引入的。

      【讨论】:

      • 我已经尝试过您的解决方案,效果很好。我可以使用 ArrayList 而不是 Vector。但最后一件事:如果我在 Vector 或 ArrayList 中有 2 个重复项,例如“13:56”和“13:56”,我如何跳过重复项并插入一次,只保持列表排序?
      • 如果您希望避免重复问题,您可能需要考虑将 Comparable 对象插入 SortedSet 而不是 List(例如 TreeSet)。这将完全避免对数据进行显式排序。
      【解决方案4】:

      创建一个java.util.Comparator 并将其提供给sort 方法。

      【讨论】:

        【解决方案5】:

        实现您自己的Comparator 类,比较两个值并调用Collections.sort(List list, Comparator c)

        【讨论】:

          【解决方案6】:

          实现您自己的 Comparator 并将其作为 Colelctions.sort 方法的第二个参数。

          【讨论】:

            【解决方案7】:

            一般来说,Java 中的对象(包括集合)会与其默认的 hashCode() 和 equals() 方法进行比较。对于内置对象和数据类型(如字符串、整数等),hashCode() 是在内部计算的,因此它们被 JLS(Java 语言规范)所保证。

            由于我们不能总是依赖于默认/内置对象,我们需要处理我们自己的自定义对象(如 Employee、Customer 等),我们应该重写 hashCode() 和 equals()方法,以便我们可以根据自定义类的对象的“最佳”相等性提供真/假。

            类似地,sort() 涉及一个比较行为,它确实需要一个 Comparator(它是一个实现 Comparator 接口的类,它带有一个覆盖方法的 compare 方法)。您还应该重写比较方法,该方法需要比较两个对象并返回结果(0 表示相等,1 表示第一个对象大于第二个对象,2 表示情况 1 的反面)。

            现在,您的数据应该以不同于正常比较的不同方式处理。您需要将数据分成两部分(使用您可以执行的拆分方法),然后您可以对这两个部分进行单独比较(冒号前的第一部分,冒号后的第二部分)。

            最后,您应该向 sort 方法提供此自定义比较器的一个实例,它最终将为您的自定义数据进行自定义排序:)

            【讨论】:

              【解决方案8】:

              我认为这很简单:

              public class NumericalStringSort {
              
                  public static void main(String[] args) {
                      List<String> input = Arrays.asList(new String[] {"17:21", "22:11", "5:34", "5:38"});
                      Collections.sort(input, new NumericalStringComparator());
                      System.out.println(input);
                  }
              
                  public static class NumericalStringComparator implements Comparator<String> {
                      public int compare(String object1, String object2) {
                          return pad(object1).compareTo(pad(object2));
                      }
              
                      private String pad(String input) {
                          return input.indexOf(":") == 1 ? "0" + input : input;
                      }
                  }
              }
              

              【讨论】:

              • 这个比较器只按照“:”前的数字排序,忽略“:”后的数字。如果还有 "5:7" 怎么办?在您的示例中,它将出现在“5:38”之后。
              • @Brad 没有给出冒号后面单个数字的示例。我假设这些是没有前导零的时间字符串。确实,如果您对输入格式做出其他假设,则需要不同的 Comparator 实现。
              【解决方案9】:

              刚刚发现这个(相当旧的)帖子,答案并没有完全解决我遇到的问题。我需要一个更通用的解决方案,因为这些值是用户输入,并且应该按照包含的数字的顺序对诸如“abc 1 a 12”和“abc 1 a 1”之类的内容进行排序。所以我写了下面的比较器:

              new Comparator<String>() {
              
                      @Override
                      public int compare(String o1, String o2) {
                          String[] s1=splitNumeric(o1);
                          String[] s2=splitNumeric(o2);
                          for (int x=0;x<s1.length&&x<s2.length;x++){
                              if (!s1[x].equals(s2[x])){
                                  if (s1[x].charAt(0)=='N' && s2[x].charAt(0)=='N'){
                                      long l1=Long.parseLong(s1[x].substring(1));
                                      long l2=Long.parseLong(s2[x].substring(1));
                                      return (int)Math.signum(l1-l2);
                                  }
                                  break;
                              }
                          }
                          return o1.compareTo(o2);
                      }
                  }
              

              虽然函数 splitNumeric 定义如下:

                 private String[] splitNumeric(String s){
                      final String numbers="0123456789";
                      LinkedList<String> out=new LinkedList<String>();
                      int state=-1;
                      for (int x=0;x<s.length();x++){
                          if (numbers.contains(s.charAt(x)+"")){
                              if (state==1)
                                  out.set(out.size()-1,out.getLast()+s.charAt(x));
                              else{
                                  state=1;
                                  out.add("N"+s.charAt(x));
                              }
                          }
                          else{
                              if (state==0)
                                  out.set(out.size()-1,out.getLast()+s.charAt(x));
                              else{
                                  state=0;
                                  out.add("S"+s.charAt(x)+"");
                              }
                          }
                      }
                      return out.toArray(new String[0]);
                  }
              

              代码将对字符串进行排序

              "X 124 B"
              "X 1 Y"
              "X 111 Z" 
              "X 12 Y"
              "12:15"
              "12:13"
              "12:1"
              "1:1"
              "2:2"
              

              如下:

              "1:1"
              "2:2"
              "12:1"
              "12:13"
              "12:15"
              "X 1 Y"
              "X 12 Y"
              "X 111 Z" 
              "X 124 B"
              

              享受:)

              【讨论】:

                猜你喜欢
                • 2023-03-10
                • 1970-01-01
                • 2020-03-21
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2019-03-28
                • 2023-02-03
                相关资源
                最近更新 更多