1

Suppose that I have a list of strings 'ABC123', 'XXY111', 'EFG001' and so on.

I need to sort these strings in 2 ways,

1) First sorted by the numbers.
2) Then it sorted by the letters.

I tried using a Comparator to sort the strings.

First I split the string and had the numbers at the beginning and then sorted the List using Collections.sort().

But I am not sure how to sort it in the two ways.

Below is my code,

  public class SortAlphaNumeric {   

    static class MyComparator implements Comparator<String> {

        @Override
        public int compare(String o1, String o2) {

            String oo1 = o1.substring(3) + o1.substring(0,3);
            String oo2 = o2.substring(3) + o2.substring(0,3);

            return oo1.compareTo(oo2);
        }       
    }

    public static void main(String[] args) {

        String str1 = "ABC123";
        String str2 = "ACB111";
        String str3 = "XXX003";
        String str4 = "XYZ001"; 
        String str5 = "CDE123";
        String str6 = "FEG111"; 


        List<String> list = new ArrayList<String>();        

        list.add(str1);
        list.add(str2);
        list.add(str3);
        list.add(str4);
        list.add(str5);
        list.add(str6);

        System.out.println("Before sorting");

        Iterator<String> itr1 = list.iterator();

        while(itr1.hasNext()) {
            System.out.println(itr1.next());
        }

        SortAlphaNumeric.MyComparator myComp = new SortAlphaNumeric.MyComparator();

        System.out.println("========================");

        Collections.sort(list, myComp); 

        System.out.println("After 1st sorting");

        Iterator<String> itr2 = list.iterator();

        while(itr2.hasNext()) {
            System.out.println(itr2.next());
        }

        Collections.sort(list, myComp);

        System.out.println("After 2nd sorting");

        Iterator<String> itr3 = list.iterator();

        while(itr3.hasNext()) {
            System.out.println(itr3.next());
        }
    }
}
N8888
  • 670
  • 2
  • 14
  • 20
RohitT
  • 187
  • 2
  • 11
  • What exactly does not work? By swapping the chars and digits, shouldn't it work exactly as you want: first sort by the number, then when it's equal sort by the chars? After sorting, the list looks like `[XYZ001, XXX003, ACB111, FEG111, ABC123, CDE123]`. What are you trying to achieve by sorting twice? Can you maybe show example data and what you expect? – Malte Hartwig Aug 06 '18 at 13:33
  • If you want it to sort after diffrent things, just use two or more seperate Comparator<> and call these one after the other from least importate to most improtant rule, as long as all ecxept the first one to call use stable sorting it should work – ProfBits Aug 06 '18 at 13:37
  • @MalteHartwig - The 2nd sorting does not produce any difference. I want it to sort on the basis of chars in the 2nd sort. Actually it was one of my interview questions. Do you think my solution is correct ?? – RohitT Aug 06 '18 at 19:32
  • I can't tell, as both your sorts do the same thing. Which is not surprising as you use the same comparator twice. Doesn't the question require you to write two separate comparators, one comparing the numbers and the other comparing the chars? Or do they mean sorting by number, and if the numbers are equal, sort by chars? In that case, you don't need that second sort, your implementation does the job for the example data. I don't know what the question means, you should ask for clarification. – Malte Hartwig Aug 06 '18 at 20:49
  • @MalteHartwig - Yes you were right. I needed to implement another Comparator. But how does a String Comparator compare the numbers ? How could i improve my logic and coding ? – RohitT Aug 07 '18 at 04:42

3 Answers3

2

You can easily compose two comparators, chaining the 0-3 index comparison on the 3-6 index comparison.

The following is an alternative using substrings instead of split (simplifying the code even further):

Comparator<String> myComp = Comparator.comparing((String s) -> s.substring(3))
                                      .thenComparing(s -> s.substring(0, 3));

// resulting in:
// XYZ001, XXX003, ACB111, FEG111, ABC123, CDE123

Assuming the texts have this standard length, the above code eliminates the need for MyComparator class.

Malte Hartwig
  • 4,477
  • 2
  • 14
  • 30
ernest_k
  • 44,416
  • 5
  • 53
  • 99
1

If you are using Java 8 you can use Comparator like this :

List<String> inputs = Arrays.asList(
        "ABC123", "ACB111", "XXX003", "XYZ001", "CDE123", "FEG111"
);    
inputs.sort(Comparator.comparing((String s) -> Integer.valueOf(s.split("(?<=\\D)(?=\\d)")[1]))
        .thenComparing(s -> s.split("(?<=\\D)(?=\\d)")[0]));

System.out.println(inputs);

Outputs

[XYZ001, XXX003, ACB111, FEG111, ABC123, CDE123]
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
1

As you have clarified, you do not want to sort once with the logic "sort by the numeric part, and if that is equal, sort by the char part", but instead it is supposed to be two different sorts. You should implement two different comparators for that.

Numeric comparison

Your current logic (splitting at index 3 and comparing the number as String) works as long as all strings have the same length and char-number-distribution. If there where elements like "ABC12" or "A12345", though, you'd run into trouble: First, you'd mix chars and digit, and second, even if you managed to split them correctly (read here how to do that), comparing numbers as String will lead to wrong results if they differ in length (e.g. 2<12, but "2">"12"). You should instead parse the number to an actual integer, then compare that. This is how a comparator for this purpose could look:

Comparator<String> byNumber = Comparator.comparingInt(o -> Integer.parseInt(getNumberPart(o)));
// look at the linked question for how to properly split, a simple
// solution might use something like o.replaceAll("\\w", "")

String comparison

As for the char part, that one is straightforward:

Comparator<String> byChars = Comparator.comparing(o -> getCharPart(o));

Now you can use those to sort your list as you like:

list.sort(byNumber); // XYZ001, XXX003, FEG111<->ACB111, ABC123<->CDE123
list.sort(byChars);  // ACB111<->ABC123, CDE123, FEG111, XYZ003<->XXX001

Note that since you only compare by either number or chars, there is uncertainty how the elements with equal number/char part will be sorted. It can change depending on parallelism or the underlying collection, for example. If you want to make sure that you always have the same order, you can combine your main comparator with a second one that is used if the first one returns 0:

list.sort(byNumber.thenComparing(byChars)); // XYZ001, XXX003, ACB111, FEG111, ABC123, CDE123
list.sort(byChars.thenComparing(byNumber)); // ACB111, ABC123, CDE123, FEG111, XYZ001, XXX003
Malte Hartwig
  • 4,477
  • 2
  • 14
  • 30