34

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.

Nishant
  • 54,584
  • 13
  • 112
  • 127
MAGx2
  • 3,149
  • 7
  • 33
  • 63

6 Answers6

68

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]
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • Why if I run your algorithm with List strings = Arrays.asList("a", "aaa"); the result is distinct than List strings = Arrays.asList("aaa", "a"); ? I believe the result should be the same. Please tell me if I am in an error... – Goose Nov 15 '16 at 03:32
  • @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 ? – Ponomarenko Oleh Sep 17 '18 at 09:50
  • This post is pretty old, but: @PonomarenkoOleh return num.isEmpty() ? 1 : Integer.parseInt(num); Try 1 instead of 0. – Ismoh Dec 04 '18 at 08:59
  • not really work with Arrays.asList("1000HoVBSTest", "366MAC_CTX", "151TBD2"); – robert trudel Sep 16 '19 at 13:39
  • So I am using this to sort my list but the issue is there are few string with decimals like 4.1 and what it is doing is it is removing dot[.] and considering it as 41 so a list with [1,2,2.1,3,4,4.1,5] it is sorting as [1,2,3,4,5,2.1,4.1]. Is there any way to remove all non numeric characters except dot[.] – antnewbee Jul 08 '20 at 07:12
  • 1
    @antnewbee I added a version of code that works for decimals. – Bohemian Jul 08 '20 at 07:30
13

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);
            }
        });
vamsi
  • 2,809
  • 2
  • 15
  • 18
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", "")));
        }

    });
Vikdor
  • 23,934
  • 10
  • 61
  • 84
RNJ
  • 15,272
  • 18
  • 86
  • 131
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]", "");
    }
}
leocborges
  • 4,799
  • 5
  • 32
  • 38
Igorry
  • 177
  • 3
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.

Itay Karo
  • 17,924
  • 4
  • 40
  • 58
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.

SkyWalker
  • 13,729
  • 18
  • 91
  • 187