问题
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?
回答1:
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);
}
}
回答2:
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.
回答3:
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
回答4:
@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
来源:https://stackoverflow.com/questions/41085394/java-comparator-alphanumeric-strings