3

I want to implement my custom Comparator to sort a map of keys in this particular way:

1
2
3
4
4a
4b
5
6
11
12
12a
...

And I dont know how can do it exactly. I have impelemented this only for numeric values:

aaa = new TreeMap<String, ArrayList<ETrack>>(
    new Comparator<String>()
    {
      @Override
      public int compare(String s1, String s2)
      {
        return Integer.valueOf(s1).compareTo(Integer.valueOf(s2));
      }
    });

But this is not obiouslly the complete solution. Any idea?

5
  • Split apart the number and alpha part. Sort on the number part then the alpha part. Commented Dec 11, 2016 at 11:00
  • 1
    Create a class (ex: Version implements Comparable<Version>) containing an int (ex: 12) and a char or String (ex: 'a'). Implement equals, hashCode and compareTo in this class. Add a static method parse(String) parsing a String to a Version. Then use a TreeMap<Version, List<ETrack>. Commented Dec 11, 2016 at 11:00
  • 1
    Possible duplicate of Sort on a string that may contain a number Commented Dec 11, 2016 at 11:01
  • Regarding splitting read here: stackoverflow.com/questions/8270784/… Commented Dec 11, 2016 at 11:02
  • 1
    You are by far not the first one to have this question. Did you search for a solution? Commented Dec 11, 2016 at 11:03

4 Answers 4

6

One approach is to map the strings to objects and let those objects implement Comparable.

You can do it simpler. If you like regular expressions, you can use this trick, even though it is not too efficient:

// Make sure not to compile the pattern within the method, to spare us some time.
Pattern p = Pattern.compile("^([0-9]+)([a-z]?)$");

/**
 * Converts a string to a sortable integer, ensuring proper ordering:
 * "1" becomes 256
 * "1a" becomes 353
 * "1b" becomes 354
 * "2" becomes 512
 * "100" becomes 25600
 * "100a" becomes 25697
 */
int getSortOrder(String s) {
  Matcher m = p.matcher(s);
  if(!m.matches()) return 0;
  int major = Integer.parseInt(m.group(1));
  int minor = m.group(2).isEmpty() ? 0 : m.group(2).charAt(0);
  return (major << 8) | minor;
}

Use it like this:

new Comparator<String>() {
  @Override
  public int compare(String s1, String s2) {
    return getSortOrder(s1) - getSortOrder(s2);
  }
}
Sign up to request clarification or add additional context in comments.

Comments

5

Here's a 1-liner:

aaa = new TreeMap<String, ArrayList<ETrack>>(Comparators
    .comparingInt(s -> Integer.parseInt(s.replaceAll("\\D", "")))
    .thenComparing(s -> s.replaceAll("\\d", "")));

FYI \D matches non-digits, \d matches digits.

Incidentally, this will work no matter the order of the numeric and non-numeric parts.

Comments

2

@bohemian answer is great, I'd only make the following change so it can be used almost anywhere. The check for numeric string is put there because you'll get an error if your input string doesn't have any number in it.

private  Comparator stringComparator = Comparator
            .comparingInt(s -> StringUtils.isNumeric(s.toString())?Integer.parseInt(s.toString().replaceAll("\\D", "")):0)
            .thenComparing(s -> s.toString().replaceAll("\\d", ""));

Note that StringUtils is from org.apache.commons.lang3.StringUtils

Comments

0

if you have only one char that is a letter and not more, you can try somthing like this:

  aaa = new TreeMap<String, ArrayList<ETrack>>(
  new Comparator<String>()
  {
  @Override
  public int compare(String s1, String s2)
  {
     String newS1=s1;
     String newS2=s2;
     char s1Letter=0;
     char s1Letter=0;
     //checks if last char is not a digit by ascii
    if(!(s1.charAt(s1.length()-1)>=48 && s1.charAt(s1.length()-1)<=57)){
        newS1 = s1.substring(0,s1.length()-1);
        s1Letter = s1.charAt(s1.length()-1);
   }
      if(!(s2.charAt(s2.length()-1)>=48 && s2.charAt(s2.length()-1)<=57)){
        newS2 = s2.substring(0,s2.length()-1);
        s2Letter = s2.charAt(s2.length()-1);
   }
   int s1Val = Integer.parseInt(newS1);
   int s2Val = Integer.parseInt(newS2);
   if(s1Val<s2Val)
      return -1;
   else if(s1Val>s2Val)
      return 1;
   else if(s1Letter > s2Letter)
      return 1;
   else if(s1Letter < s2Letter)
      return -1;
   else
      retrurn 0;

  }
});

hope that helps :) there might be a more elegant solution but i believe this will work for you if I understand the problem you're facing

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.