简体   繁体   中英

How to sort a String collection that contains numbers?

I have a String Vector that contains data like this :

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

If i try to merge this using Collections.sort( ... ); it will appear like this :

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

Actually i want it to appear like this :

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

So i want to sort the elements according to the number before the colon ":" then if some elements have the same number before ":" then sort them according to the number after the ":".

What is the simplest way to do this ?

The correct way to do this is to not store non-string values as strings.

The data in your collection has some structure and rules and can't be any arbitrary string. Therefore you should not use the String data type.

Let's define a type called TwoNumbers (because I don't know what the type should represent, even if I could guess):

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

The compareTo method exists as the implementation of the Comparable interface : it defines how objects of this type are ordered.

I've used the final fields (and don't provide setters), because the class implements immutable objects .

This way you can directly sort your data without an additional Comparator and don't need to distribute all that "split and parse" code all over your program. Instead you have a single class that's responsible for handling that specific format and all the other pieces of code can just use that.

This is horribly inefficient, but it should do the job.

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

(Hint: if it were my code, I'd optimize it to use substrings instead of String.split(), but I'm too lazy)

You could either create a custom Comparator to split the String and parse it into two ints, or create a bespoke class to represent each String and store that in the Collection instead. I favour the latter approach as you only incur the overhead of splitting / parsing the String once; eg

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

Then it's simply a case of storing some Data objects in your Collection ; eg

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

One more thing - You mention you're using a Vector . You should try to avoid using Vector / Hashtable as these have been superseded by List / Map , which were introduced as part of the Collections Framework in JDK 1.2.

Just found this (quite old) post and the answers didn't quite solve the problem I have. I needed a more generic solution, as the values were user inputs and something like "abc 1 a 12" and "abc 1 a 1" should be sorted in order of the contained number(s). So I wrote the following Comparator:

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

While the function splitNumeric is defined as follows:

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

The code will sort Strings

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

as follows:

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

Enjoy :)

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

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

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

Generally, objects in Java (including Collections) are compared with their default hashCode() and equals() method. For the built in objects and data types (like String, Integet etc.,) the hashCode() is computed internally and hence they are used as guaranteed by the JLS (Java Language Specification).

As we can't always be dependent upon the default/built in objects and we need to deal with our own custom objects (like Employee, Customer etc.,), we should have to override hashCode() and equals() method, so that we can provide the true/false according to the "BEST" equality of the objects of our custom classes.

Similary, sort() involves a comparison act that indeed needs a Comparator (which is a class implementing the Comparator interface with an overridden method of compare method). You should also override the compare method that takes two Objects to be compared and returns a result (0 for equal, 1 for the 1st object being greater than the second, 2 for the reverse of case 1).

Now, you data should be dealt in a different way which is quite away from the normal comparsion. You need to split the data into two parts (using a split method you can do) and then you can do the individual comparison on the two parats (first part before the colon, second part after the colon).

Finally, you should provide an instance of this custom comparator to the sort method, that will eventually do the custom sorting for your custom data :)

I think this is pretty simple:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM