如何对包含数字的字符串集合进行排序?

3
我有一个String向量,其包含以下数据:
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
因此,我想按照冒号“:”前的数字对元素进行排序,然后如果某些元素在“:”之前具有相同的数字,则根据“:”后面的数字进行排序。
最简单的方法是什么?
9个回答

7

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

您的集合中的数据具有一定的结构和规则,不能是任意的字符串。因此,您不应该使用 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 方法是实现 Comparable 接口 的一部分:它定义了此类型对象的排序方式。

我使用了 final 字段(并且不提供 setters),因为该类实现了不可变对象

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


@Joachim .. 感谢您提供的好解决方案。只有两个问题:为什么要使用“final”字段?以及“compare(...)”方法是什么? - Brad
@Brad: 我已经更新了我的答案,并附上了一些细节和链接。 - Joachim Sauer

4
这样做非常低效,但它应该能够完成工作。
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(),但我太懒了。)
(提示:如果是我的代码,我会优化它使用子字符串而不是 String.split(),但我太懒了)

2

您可以创建一个自定义的Comparator来将String拆分并解析为两个整数,或者创建一个专门的类来表示每个String并将其存储在Collection中。我更喜欢后一种方法,因为您只需要一次拆分/解析字符串的开销;例如:

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

接下来只需将一些 Data 对象存储在您的 Collection 中即可,例如:

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中有两个重复项,例如“13:56”和“13:56”,如何跳过重复项并仅插入一次,同时保持列表排序? - Brad
2
如果您希望避免重复问题,您可能希望考虑将Comparable对象插入SortedSet而不是List(例如TreeSet)。这将避免必须显式地对数据进行排序。 - Adamski

0

我认为这很简单:

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
@Brad 没有给出冒号后面的单个数字的示例。我假设这些是没有前导零的时间字符串。如果您对输入格式进行其他假设,那么您需要不同的比较器实现。 - Adriaan Koster

0

刚刚发现了这篇(相当古老的)帖子,但是答案并没有完全解决我的问题。我需要一个更通用的解决方案,因为值是用户输入的,像“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"

祝您愉快 :)


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

0

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


0

实现自己的比较器,并将其作为第二个参数传递给Colelctions.sort方法。


0

通常,在Java中(包括集合),对象是使用它们的默认hashCode()和equals()方法进行比较的。对于内置对象和数据类型(如String、Integer等),hashCode()是在内部计算的,因此它们是由JLS(Java语言规范)保证的。

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

同样,sort()涉及到一个比较操作,确实需要一个Comparator(这是一个实现了Comparator接口并具有compare方法的类)。您还应该重写compare方法,该方法接受两个要比较的对象并返回结果(相等为0,第一个对象大于第二个对象为1,反之为2)。

现在,您的数据应该以一种与正常比较相去甚远的方式处理。您需要将数据分成两部分(使用split方法即可),然后可以对两个部分(冒号前的第一部分,冒号后的第二部分)进行单独比较。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接