35

I have problem with default comparator for Strings (in SortedSet). The problem is that default comparator doesn't sort good String that contains numbers i.e.: In set i have:

room1, room2, room100

Natural ordering should be like above but in set I have:

room1, room100, room2

I know why it is but I don't know how to change it.

3
  • 6
    You need to create a custom comparartor Commented Dec 20, 2012 at 13:44
  • I know but i don't have idea how to compare it. Know I'm trying with this: >private int compareNumbers(String o1, String o2) { Pattern numberPattern = Pattern.compile(PATTERN); Matcher matcher1 = numberPattern.matcher(o1); Matcher matcher2 = numberPattern.matcher(o2); int i1 = Integer.parseInt(matcher1.group()); int i2 = Integer.parseInt(matcher2.group()); System.out.println(i1 + " " + i2); return i1 - i2; } Commented Dec 20, 2012 at 13:45
  • You need to make assumptions, such as, does all string in the format of <some chars><a number> ? or decide on what exactly your format is, only then you can write your comparator Commented Dec 20, 2012 at 13:47

6 Answers 6

70

Try this comparator, which removes all non-digit characters then compares the remaining characters as numbers:

Collections.sort(strings, 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 0 if no digits found
        return num.isEmpty() ? 0 : Integer.parseInt(num);
    }
});

When this code is run:

public static void main(String[] args) {
    List<String> strings = Arrays.asList("room1.2", "foo1.1", "foo", "room2.3", "room100.999", "room10", "room.3");

    Collections.sort(strings, 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 0 if no digits found
            return num.isEmpty() ? 0 : Integer.parseInt(num);
        }
    });
    System.out.println(strings);
}

This is the output:

[foo, room.3, room10, foo1.1, room1.2, room2.3, room100.999]

When the numbers are decimals, retain both digits and dots.

When this code (which shows use of a function instead of an anonymous class) is run:

public static void main(String[] args) {
    List<String> strings = Arrays.asList("room1.2", "foo1.1", "room2.3", "room100.999", "room10", "room.3");
    Collections.sort(strings, Comparator.comparing(Application::extractDouble));
    System.out.println(strings);
}

static double extractDouble(String s) {
    String num = s.replaceAll("[^\\d.]", "");
    // return 0 if no digits found
    return num.isEmpty() ? 0 : Double.parseDouble(num);
}

This is the output:

[room.3, foo1.1, room1.2, room2.3, room10, room100.999]
Sign up to request clarification or add additional context in comments.

6 Comments

Why if I run your algorithm with List<String> strings = Arrays.asList("a", "aaa"); the result is distinct than List<String> strings = Arrays.asList("aaa", "a"); ? I believe the result should be the same. Please tell me if I am in an error...
@Bohemian How can I sort this row but in order like this: [room1, room2, room10, room100, foo] First - reverse alphabet Second - by number in order ?
This post is pretty old, but: @PonomarenkoOleh return num.isEmpty() ? 1 : Integer.parseInt(num); Try 1 instead of 0.
not really work with Arrays.asList("1000HoVBSTest", "366MAC_CTX", "151TBD2");
@antnewbee I added a version of code that works for decimals.
|
14

Used @bohemian answer. Just improved a bit. This worked for me very well..

        Collections.sort(asdf, new Comparator<String>() {
            public int compare(String o1, String o2) {

                String o1StringPart = o1.replaceAll("\\d", "");
                String o2StringPart = o2.replaceAll("\\d", "");


                if(o1StringPart.equalsIgnoreCase(o2StringPart))
                {
                    return extractInt(o1) - extractInt(o2);
                }
                return o1.compareTo(o2);
            }

            int extractInt(String s) {
                String num = s.replaceAll("\\D", "");
                // return 0 if no digits found
                return num.isEmpty() ? 0 : Integer.parseInt(num);
            }
        });

2 Comments

In this case, if you have "q2ab" and "qa2b" they will be equivalent, but they are not.
Works only when the content to sort has a string prefix and an integer suffix, but it fits my current needs.
6

try this. I've assumed that you will always have "room" at the start of your string.

    List<String> list = Arrays.asList("room1", "room100", "room2");
    Collections.sort(list, new Comparator<String>()
    {
        @Override
        public int compare(String o1, String o2)
        {
            return new Integer(o1.replaceAll("room", ""))
                .compareTo(new Integer(o2.replaceAll("room", "")));
        }

    });

Comments

2

Here is my Comparator implementation for such a sort: (strings can start from any chars)

public class StringNumberComparator implements Comparator<String>{

    @Override
    public int compare(String o1, String o2) {
    int i1 = this.getRearInt(o1);
    int i2 = getLeadingInt(o2);
    String s1 = getTrailingString(o1);
    String s2 = getTrailingString(o2);

    if(i1==i2)
         return s1.compareTo(s2);
    if(i1>i2)
         return 1;
    else if(i1<i2)
            return -1;
    return 0;
    }

    private int getRearInt(String s) {
    s=s.trim();
    int i=Integer.MAX_VALUE;
    try {
             i = Integer.parseInt(s.split("[^0-9]+")[1]);
    } catch(ArrayIndexOutOfBoundsException e) {

    } catch(NumberFormatException f) {
            return i;
    }

    return i;
    }

    private String getTrailingString(String s) {
        return  s.replaceFirst("[0-9]", "");
    }
}

Comments

0

You can implement a comparator and pass it to the set constructor. See http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Comparator.html.

If all your strings are in the form of room[number] you can strip the "room" parse the number and compare by it.
Alternatively - you can store Integers in you set and print them with "room" prefix.

2 Comments

But I don't have idea how to compare it. Some regex?
Are the strings always in the form of room[number]?
0

A lazy alternative would be to make the String comparator work without doing anything extra (defining your own comparator). You can have that by padding with zeros the numbers within your String like this: room0001, room0002, room0100 then the default String comparator will work. You do however, need to know the maximum number value so you can adapt your padding accordingly.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.