I have my code for "Find the missing integer" in Codility
public static int solution(int[] A) {
ArrayList<Integer> a = new ArrayList<Integer>();
for(int i=0; i<A.length; i++) if(A[i] >= 0) a.add(A[i]);
if(a.isEmpty()) {
return 1;
}
a.sort(null);
if(a.get(0) > 1) {
return 1;
}
for(int i=0; i<a.size()-1; i++) {
if(a.get(i) != a.get(i+1) && a.get(i)+1 != a.get(i+1)) {
return a.get(i)+1;
}
}
return a.get(a.size()-1)+1;
}
This code works for all except Performance tests - large_1.
It gives me an error "got 233 expected 40000".
When i replace this code:
if(a.get(i) != a.get(i+1) && a.get(i)+1 != a.get(i+1))
return a.get(i) +1;
with
int a1 = a.get(i);
int a2 = a.get(i+1);
if(a1 != a2 && a1 +1 != a2) return a.get(i) +1;
or
int sub = a.get(i+1) - a.get(i);
if(sub != 0 && sub != 1) return a.get(i) +1;
then there are no errors.(I got 100/100 score when i replace that line)
Is there anyone who can give some explanation for the difference?
They seem the same to me.
if(a.get(i) != a.get(i+1) && a.get(i)+1 != a.get(i+1))
Because a is an ArrayList<Integer>, a.get(i) is an Integer, so you're comparing Integers by identity here.
When you added the array elements to the list using a.add(A[i]), they were auto-boxed: the compiler rewrote this to a.add(Integer.valueOf(A[i])).
Only Integers in the range -128..127 are guaranteed to be cached by Integer.valueOf; so if the Integer's value is outside this range, you will be comparing Integers that have equal value but different identities.
On the other hand:
int a1 = a.get(i); // Unboxing: int a1 = a.get(i).intValue()
int a2 = a.get(i+1); // Unboxing: int a2 = a.get(i+1).intValue()
if(a1 != a2 && a1 +1 != a2)
a1 and a2 are primitives - you unbox them by assigning them to int variables - so it's fine to compare them by == or !=.
In your first version, replace A != B with !A.equals(B) (or !Objects.equals(A, B)).
Your if condition comparing the Integer objects which obliviously compare the address location of the objects instead of the values. And this is i am sure not expected by you.
Change your if condition to below -
if(a.get(i).intValue() != a.get(i+1).intValue() && a.get(i).intValue()+1 != a.get(i+1).intValue())
Related
I have a method returning a list of String that need to be sorted. However, I'm running into the old String number sorting issue and was wondering if any one could assist with a Comparator implementation or point me in the direction of one.
The list is going to return something list this:
State Lower Legislative District 1
State Lower Legislative District 11
State Lower Legislative District 12
...
State Lower Legislative District 2
...
State Lower Legislative District 100
...
State Upper Legislative District 1
State Upper Legislative District 11
...
So, first I need to do a basic String sort, but then I need to sort by the number. The number to sort on should always trail, and may be 2 or 3 digits.
(Edit) My initial thought is to split the string on space, run StringUtils.isNumeric on the number portion, then sort. However, it seems a bit of a kludge to me.
Can anyone assist?
There is an article about this on Coding Horror. This is called natural sorting, where you effectively treat a group of digits as a single "character". See this question for some Java implementations of the idea.
Sorting for Humans : Natural Sort Order
The default sort functions in almost every programming language are poorly suited for human consumption. What do I mean by that? Well, consider the difference between sorting filenames in Windows explorer, and sorting those very same filenames via Array.Sort() code:
continued...
I wrote a variation on String.CompareTo that compares the length of numbers found in the two strings. When encounting two numbers of the same length the alphanumeric compare is resumed as normal. It also skips leading zeros.
public static int compareNatural(String a, String b) {
int la = a.length();
int lb = b.length();
int ka = 0;
int kb = 0;
while (true) {
if (ka == la)
return kb == lb ? 0 : -1;
if (kb == lb)
return 1;
if (a.charAt(ka) >= '0' && a.charAt(ka) <= '9' && b.charAt(kb) >= '0' && b.charAt(kb) <= '9') {
int na = 0;
int nb = 0;
while (ka < la && a.charAt(ka) == '0')
ka++;
while (ka + na < la && a.charAt(ka + na) >= '0' && a.charAt(ka + na) <= '9')
na++;
while (kb < lb && b.charAt(kb) == '0')
kb++;
while (kb + nb < lb && b.charAt(kb + nb) >= '0' && b.charAt(kb + nb) <= '9')
nb++;
if (na > nb)
return 1;
if (nb > na)
return -1;
if (ka == la)
return kb == lb ? 0 : -1;
if (kb == lb)
return 1;
}
if (a.charAt(ka) != b.charAt(kb))
return a.charAt(ka) - b.charAt(kb);
ka++;
kb++;
}
}
One way would be to use a simple regex to parse out the fields of interest in your comparator and then compare them manually. Here's an untested example:
private static final Pattern pattern = Pattern.compile("^State (Lower|Upper) Legislative District (\\d+)$");
public int compare(String a, String b) {
Matcher matcher1 = pattern.matcher(a);
Matcher matcher2 = pattern.matcher(b);
if( matcher1.matches() && matcher2.matches() ) {
//compare upper/lower
int upperLowerComparison = matcher1.group(1).compareTo(matcher2.group(1));
if ( upperLowerComparison != 0 ) {
return upperLowerComparison;
}
//number comparison
return Integer.valueOf(matcher1.group(2)).compareTo(Integer.valueOf(matcher2.group(2));
}
//...what to do if they don't match?
}
You have two options. The first one is to create a class having two fields - the name and the number. Of course first parse the name and numbers. Then in the comparator first compare the name and then the number. The second one is to do the parsing at place in the compare method. Choose which one is more appropriate to you.
Have a look at this implementation:
public static int naturalCompare(String a, String b, boolean ignoreCase) {
if (ignoreCase) {
a = a.toLowerCase();
b = b.toLowerCase();
}
int aLength = a.length();
int bLength = b.length();
int minSize = Math.min(aLength, bLength);
char aChar, bChar;
boolean aNumber, bNumber;
boolean asNumeric = false;
int lastNumericCompare = 0;
for (int i = 0; i < minSize; i++) {
aChar = a.charAt(i);
bChar = b.charAt(i);
aNumber = aChar >= '0' && aChar <= '9';
bNumber = bChar >= '0' && bChar <= '9';
if (asNumeric)
if (aNumber && bNumber) {
if (lastNumericCompare == 0)
lastNumericCompare = aChar - bChar;
} else if (aNumber)
return 1;
else if (bNumber)
return -1;
else if (lastNumericCompare == 0) {
if (aChar != bChar)
return aChar - bChar;
asNumeric = false;
} else
return lastNumericCompare;
else if (aNumber && bNumber) {
asNumeric = true;
if (lastNumericCompare == 0)
lastNumericCompare = aChar - bChar;
} else if (aChar != bChar)
return aChar - bChar;
}
if (asNumeric)
if (aLength > bLength && a.charAt(bLength) >= '0' && a.charAt(bLength) <= '9') // as number
return 1; // a has bigger size, thus b is smaller
else if (bLength > aLength && b.charAt(aLength) >= '0' && b.charAt(aLength) <= '9') // as number
return -1; // b has bigger size, thus a is smaller
else
return lastNumericCompare;
else
return aLength - bLength;
}
It should be fast, without any regular expressions or array manipulation, just a couple of flags and a lot of cases.
This should sort any combination of numbers inside strings and properly support numbers which are equal and move on.
I usually do this by prefixing zeros to the number and handle the whole entity as a string. then sort it.
See this:
public abstract class MyNumberComparator {
protected int doCompare(final String number1, final String number2) {
String strNumber1 = fillUpLeftWithZeros(number1, 30);
String strNumber2 = fillUpLeftWithZeros(number2, 30);
return strNumber1.toUpperCase().compareTo(strNumber2.toUpperCase());
}
}
A simple implementation would be like this one (this works with any string that ends with a number):
public class SplitComparator implements Comparator<String> {
static class Pair implements Comparable<Pair> {
private String name;
private Integer number;
public Pair(String value) {
value = value.trim();
this.name = value.substring( 0, value.lastIndexOf(" ") );
this.number = Integer.valueOf( value.substring( value.lastIndexOf(" ") + 1, value.length() ) );
}
#Override
public int compareTo( Pair right) {
int result = this.name.compareTo( right.name );
if ( result == 0 ) {
result = this.number.compareTo( right.number );
}
return result;
}
}
#Override
public int compare(String left, String right) {
return new Pair( left ).compareTo( new Pair( right ) );
}
public static void main( String ... args ) {
String[] values = { "State Lower Legislative District 1",
"State Lower Legislative District 11",
"State Upper Legislative District 1",
"State Upper Legislative District 11"};
SplitComparator comparator = new SplitComparator();
System.out.println( comparator.compare( values[1] , values[0]) );
System.out.println( comparator.compare( values[0] , values[1]) );
System.out.println( comparator.compare( values[0] , values[3]) );
}
}
I'm doing a java project to test different sorting algorithms and it is needed to use different typed of vector.
I determine the type of the vector whithin the code, so I need to declare it inside a loop. This is generating an error because of the array's scope. Is there anyway to make something similar or solving this error?
I'm on eclipse 2020, here is the code that is generating the error:
if(TypeOfVector(desorganized) == -1) {
int[] organizedVector = PassingToIntVector(desorganized);}
if(TypeOfVector(desorganized) == 0) {
float[] organizedVector = PassingToFloatVector(desorganized);}
if(TypeOfVector(desorganized) == 1) {
double[] organizedVector = PassingToDoubleVector(desorganized);}
int organized = 0;
int duo;
int n = desorganized.size();
while(organized != n-1) {
organized = 0;
for (duo = 0; duo < n - 1; duo ++) {
if(organizedVector[duo] > organizedVector[duo + 1]) {
organizedVector[duo] = organizedVector[duo] - organizedoVector[duo +1];
organizedVector[duo +1] = organizedVector[duo + 1] + organizedVector[duo];
organizedVector[duo] = organizedVector[duo + 1] - organizedVector[duo];
}
else organized ++;
}
And here is an example of one of the PassingTo functions:
public float[] PassingToFloatVector(ArrayList<Object> list) {
Object[] array = list.toArray();
float[] desorganized = new float[list.size()];
for(int i = 0; i < list.size(); i++) {
desorganized[i] = (float) array[i];
}
return desorganized;
The first issue that you're asking about is scope. You need to declare your variable with scope to continue on.
if(TypeOfVector(desorganized) == -1) {
int[] organizedVector = PassingToIntVector(desorganized);
}
if(TypeOfVector(desorganized) == 0) {
float[] organizedVector = PassingToFloatVector(desorganized);
}
This would need to be changed because you're declaring your arrays within the if blocks and they will be lost once you leave the if block. ie They go out of scope.
int[] organizedVector;
if(TypeOfVector(desorganized) == -1) {
organizedVector = PassingToIntVector(desorganized);
}
if(TypeOfVector(desorganized) == 0) {
organizedVector = PassingToFloatVector(desorganized);
}
This will put organizedVector in a larger scope and you can use it after the if statements but you cannot assign it to a float[], so the second if statement will fail. Consider sticking with a List<Integer>, List<Float>, List<Double> which are all List<? extends Comparable>.
Then you can compare the elements, and swap them with collections.swap as necessary. Note that primitive arrays are not interchangeable. You'll have to write three different blocks of code to handle the three different array types.
I'm new to java.
Can anybody tell me that is the easiest way to compare two string except one character?
like:
'test' 'text' //only one character different
should return true
==============================
like input:
'test' 'txxt' //two character different return false
should return false
I know we can compare with a for loop. Is there any other way to do that?
Thx for your help. : )
Assuming the Strings are the same size, here is a solution. This solution will need to be altered slightly for uneven String lengths
boolean compareStrings(String str1, String str2) {
if (str1.length() != str2.length())
return false;
int differences = 0;
for (int i = 0; i < str1.length(); i++) {
if(str1.charAt(i) != str2.charAt(i))
if(++differences > 1)
return false;
}
//if the execution is here, then there are 0, or 1 differences, so return true
return true;
}
Try this method.
It should work for every string combination but, depending on usage, maybe a performance tuning is needed.
public static boolean compare(String s1, String s2) {
if((s1 != null && s2==null) || (s1 == null && s2!=null)){
//exact one is null
return false;
}
if((s1 == null && s2==null) || s1.equals(s2)){
//both are null or equal
return true;
}
if(Math.abs(s1.length() - s2.length()) > 1){
//A different length of more than one is more than one difference, right ?
return false;
}
//Here you have two different strings. Maybe one is a character larger than the other.
if(s1.length() != s2.length()) {
//They differ in length, so they must be equal in the first minLen charcaters.
int minLen = Math.min(s1.length(), s2.length());
return s1.substring(0,minLen).equals(s2.substring(0,minLen));
}
//Here you have two different strings of the same length.
int diff = 0;
for(int i = 0; i < s1.length() && diff < 2; i++){
if(s1.charAt(i) != s2.charAt(i)){
diff++;
}
}
return diff < 2;
}
We have code with complex Comparators that have been used for sorting java objects throughout our application. Historically these have worked, but since the introduction of TimSort in Java 7 we occasionally get the Comparison method violates its general contract! error.. depending on what data is held within the object.
Here is an example of one of our legacy Comparators (which could be almost a decade old - excuse the dodgyiness):
public int compare(TemplateBean b1, TemplateBean b2) {
// avoid null pointer exceptions
if (b1 == null && b2 == null) return 0;
if (b1 == null) return 1;
if (b2 == null) return -1;
int cmp = 0;
if ("UNATTACHED".equals(b1.getStatusCode()) &&
!"UNATTACHED".equals(b2.getStatusCode())) {
cmp = 1;
}
if (!"UNATTACHED".equals(b1.getStatusCode()) &&
"UNATTACHED".equals(b2.getStatusCode())) {
cmp = -1;
}
if (!"UNATTACHED".equals(b1.getStatusCode()) &&
!"UNATTACHED".equals(b2.getStatusCode()) &&
!"FIELDSIMPLE".equals(b1.getRefRltshpTypeCode()) &&
!"FIELDSIMPLE".equals(b2.getRefRltshpTypeCode()) &&
!"CUSTOM".equals(b1.getRefRltshpTypeCode()) &&
!"CUSTOM".equals(b2.getRefRltshpTypeCode()) &&
!"FUNCTION".equals(b1.getRefRltshpTypeCode()) &&
!"FUNCTION".equals(b2.getRefRltshpTypeCode())) {
String parent1 = b1.getGroupCode() == null ? "" : b1.getGroupCode().toUpperCase();
String parent2 = b2.getGroupCode() == null ? "" : b2.getGroupCode().toUpperCase();
cmp = parent1.compareTo(parent2);
}
if (cmp == 0) {
Integer i1 = b1.getSortOrder() == null ? Const.ZERO : b1.getSortOrder();
Integer i2 = b2.getSortOrder() == null ? Const.ZERO : b2.getSortOrder();
cmp = i1.compareTo(i2);
}
if (cmp == 0) {
String s1 = b1.getShortDescription();
if (s1 == null) s1 = "";
String s2 = b2.getShortDescription();
if (s2 == null) s2 = "";
cmp = s1.compareToIgnoreCase(s2);
}
return cmp; }
So, I want to replicate this functionality, but with a Comparator that is safe for use with TimSort.
From the code you can see that there a multiple levels to this comparison..
It will compare the group codes.
If Group codes are the same it will compare sort order.
If Sort order is the same it will compare description.
Which means it will return the result of the compare at a particular level. Which might be the comparison result of two strings or two integers. I think this is what is breaking the TimSort.
The only way I have been able to make this Comparator work around the General Contract issue is by hashing the contents of the bean and performing a string comparison. Other ideas have included writing our own sorting function.. Surely there is a better way?
Should the bean be constructed in another way to support this?
The main problem with the above Comparator is that it is not transitive. It may seem to 'work' on older JDKs because they didn't provide detection for broken comparators, but it just couldn't work correctly in general case and buggy behavior was not revealed until JDK 7.
The source of its non-transitiveness is in conditional comparison on groupCode property.
Consider situation when comparator orders objects A and B as A < B due to sortOrder field omitting comparison by groupCode because "FUNCTION".equals(B.getRefRltshpTypeCode()) and
objects B and C are ordered as B < C due to sortOrder. But it is possible that A and C when compared directly are ordered as C < A due to groupCode comparison. And this breaks transitivity requirement for Comparator.
To fix this problem groupCode should be always taken into account and every object for which groupCode is skipped due to refRltshpTypeCode value should be treated for example as smaller than any object for which groupCode is now used for comparison.
Compare method should looks something like (this is just to give you the idea):
public int compare(TemplateBean b1, TemplateBean b2) {
// avoid null pointer exceptions
if (b1 == null && b2 == null) return 0;
if (b1 == null) return 1;
if (b2 == null) return -1;
int cmp = 0;
if ("UNATTACHED".equals(b1.getStatusCode()) &&
!"UNATTACHED".equals(b2.getStatusCode())) {
cmp = 1;
}
if (!"UNATTACHED".equals(b1.getStatusCode()) &&
"UNATTACHED".equals(b2.getStatusCode())) {
cmp = -1;
}
if (shouldBeComparenByGroupCode(b1) != shouldBeComparedByGroupCode(b2)) {
if (!shouldBeComparenByGroupCode(b1)) {
return -1;
} else {
return 1;
}
}
if (shouldBeComparenByGroupCode(b1) && shouldBeComparenByGroupCode(b2)) {
String parent1 = b1.getGroupCode() == null ? "" : b1.getGroupCode().toUpperCase();
String parent2 = b2.getGroupCode() == null ? "" : b2.getGroupCode().toUpperCase();
cmp = parent1.compareTo(parent2);
}
if (cmp == 0) {
Integer i1 = b1.getSortOrder() == null ? Const.ZERO : b1.getSortOrder();
Integer i2 = b2.getSortOrder() == null ? Const.ZERO : b2.getSortOrder();
cmp = i1.compareTo(i2);
}
if (cmp == 0) {
String s1 = b1.getShortDescription();
if (s1 == null) s1 = "";
String s2 = b2.getShortDescription();
if (s2 == null) s2 = "";
cmp = s1.compareToIgnoreCase(s2);
}
return cmp;
}
where
private static boolean shouldBeComparenByGroupCode(TemplateBean b1) {
return !"UNATTACHED".equals(b1.getStatusCode()) &&
!"FIELDSIMPLE".equals(b1.getRefRltshpTypeCode()) &&
!"CUSTOM".equals(b1.getRefRltshpTypeCode()) &&
!"FUNCTION".equals(b1.getRefRltshpTypeCode());
}
The answer from #RomanKonovai is correct, however adding some more details.
Think about how the code compares these three objects, and assume that all non-reference:
A B C
Status UNATTACHED UNATTACHED UNATTACHED
RefRltshpType CUSTOM FUNCTION CUSTOM
Group Cat Ball Apple
SortOrder 10 20 30
Going through the implementation in the question, we can see that A < B, and B < C, and C < A. In other words A < B < C < A, or A < A. This is clearly not logical, and happens since depending on the values of Status and RefRltshpType the sort order is determined by either Group or SortOrder, and there is nothing to tie these two together. In essence this means that your sort order is undefined, since the result is entirely dependent on what order the input is in, that is sort(sort(List)) may not give the same result as sort(List).
The way to fix this is to do something like:
private int objectCompare(String allowed, Comparable v1, Comparable v2) {
if (v1 == v2) return 0;
if (v1 == null) return 1;
if (v2 == null) return -1;
boolean c1 = v1.equals(allowed);
boolean c2 = v2.equals(allowed);
return c1 ? c2 ? 0 : 1 : c2 ? -1 : 0;
}
private int objectCompare(Comparable v1, Comparable v2) {
if (v1 == v2) return 0;
if (v1 == null) return 1;
if (v2 == null) return -1;
return v1.compare(v2);
}
public int compare(TemplateBean b1, TemplateBean b2) {
// avoid null pointer exceptions
if (b1 == b2) return 0;
if (b1 == null) return 1;
if (b2 == null) return -1;
int cmp = objectCompare("UNATTACHED", b1.getStatusCode(), b2.getStatusCode());
if (cmp == 0) {
cmp = objectCompare("FIELDSIMPLE", b1.getRefRltshpTypeCode(), b2.getRefRltshpTypeCode());
if (cmp == 0) {
cmp = objectCompare("CUSTOM", b1.getRefRltshpTypeCode(), b2.getRefRltshpTypeCode());
if (cmp == 0) {
cmp = objectCompare("FUNCTION", b1.getRefRltshpTypeCode(), b2.getRefRltshpTypeCode());
if (cmp == 0) {
cmp = objectCompare(b1.getGroupCode(), b2.getGroupCode());
if (cmp == 0) {
cmp = objectCompare(b1.getSortOrder(), b2.getSortOrder());
if (cmp == 0) {
cmp = objectCompare(b1.getShortDescription(), b2.getShortDescription());
}
}
}
}
}
}
return cmp;
}
I have a method returning a list of String that need to be sorted. However, I'm running into the old String number sorting issue and was wondering if any one could assist with a Comparator implementation or point me in the direction of one.
The list is going to return something list this:
State Lower Legislative District 1
State Lower Legislative District 11
State Lower Legislative District 12
...
State Lower Legislative District 2
...
State Lower Legislative District 100
...
State Upper Legislative District 1
State Upper Legislative District 11
...
So, first I need to do a basic String sort, but then I need to sort by the number. The number to sort on should always trail, and may be 2 or 3 digits.
(Edit) My initial thought is to split the string on space, run StringUtils.isNumeric on the number portion, then sort. However, it seems a bit of a kludge to me.
Can anyone assist?
There is an article about this on Coding Horror. This is called natural sorting, where you effectively treat a group of digits as a single "character". See this question for some Java implementations of the idea.
Sorting for Humans : Natural Sort Order
The default sort functions in almost every programming language are poorly suited for human consumption. What do I mean by that? Well, consider the difference between sorting filenames in Windows explorer, and sorting those very same filenames via Array.Sort() code:
continued...
I wrote a variation on String.CompareTo that compares the length of numbers found in the two strings. When encounting two numbers of the same length the alphanumeric compare is resumed as normal. It also skips leading zeros.
public static int compareNatural(String a, String b) {
int la = a.length();
int lb = b.length();
int ka = 0;
int kb = 0;
while (true) {
if (ka == la)
return kb == lb ? 0 : -1;
if (kb == lb)
return 1;
if (a.charAt(ka) >= '0' && a.charAt(ka) <= '9' && b.charAt(kb) >= '0' && b.charAt(kb) <= '9') {
int na = 0;
int nb = 0;
while (ka < la && a.charAt(ka) == '0')
ka++;
while (ka + na < la && a.charAt(ka + na) >= '0' && a.charAt(ka + na) <= '9')
na++;
while (kb < lb && b.charAt(kb) == '0')
kb++;
while (kb + nb < lb && b.charAt(kb + nb) >= '0' && b.charAt(kb + nb) <= '9')
nb++;
if (na > nb)
return 1;
if (nb > na)
return -1;
if (ka == la)
return kb == lb ? 0 : -1;
if (kb == lb)
return 1;
}
if (a.charAt(ka) != b.charAt(kb))
return a.charAt(ka) - b.charAt(kb);
ka++;
kb++;
}
}
One way would be to use a simple regex to parse out the fields of interest in your comparator and then compare them manually. Here's an untested example:
private static final Pattern pattern = Pattern.compile("^State (Lower|Upper) Legislative District (\\d+)$");
public int compare(String a, String b) {
Matcher matcher1 = pattern.matcher(a);
Matcher matcher2 = pattern.matcher(b);
if( matcher1.matches() && matcher2.matches() ) {
//compare upper/lower
int upperLowerComparison = matcher1.group(1).compareTo(matcher2.group(1));
if ( upperLowerComparison != 0 ) {
return upperLowerComparison;
}
//number comparison
return Integer.valueOf(matcher1.group(2)).compareTo(Integer.valueOf(matcher2.group(2));
}
//...what to do if they don't match?
}
You have two options. The first one is to create a class having two fields - the name and the number. Of course first parse the name and numbers. Then in the comparator first compare the name and then the number. The second one is to do the parsing at place in the compare method. Choose which one is more appropriate to you.
Have a look at this implementation:
public static int naturalCompare(String a, String b, boolean ignoreCase) {
if (ignoreCase) {
a = a.toLowerCase();
b = b.toLowerCase();
}
int aLength = a.length();
int bLength = b.length();
int minSize = Math.min(aLength, bLength);
char aChar, bChar;
boolean aNumber, bNumber;
boolean asNumeric = false;
int lastNumericCompare = 0;
for (int i = 0; i < minSize; i++) {
aChar = a.charAt(i);
bChar = b.charAt(i);
aNumber = aChar >= '0' && aChar <= '9';
bNumber = bChar >= '0' && bChar <= '9';
if (asNumeric)
if (aNumber && bNumber) {
if (lastNumericCompare == 0)
lastNumericCompare = aChar - bChar;
} else if (aNumber)
return 1;
else if (bNumber)
return -1;
else if (lastNumericCompare == 0) {
if (aChar != bChar)
return aChar - bChar;
asNumeric = false;
} else
return lastNumericCompare;
else if (aNumber && bNumber) {
asNumeric = true;
if (lastNumericCompare == 0)
lastNumericCompare = aChar - bChar;
} else if (aChar != bChar)
return aChar - bChar;
}
if (asNumeric)
if (aLength > bLength && a.charAt(bLength) >= '0' && a.charAt(bLength) <= '9') // as number
return 1; // a has bigger size, thus b is smaller
else if (bLength > aLength && b.charAt(aLength) >= '0' && b.charAt(aLength) <= '9') // as number
return -1; // b has bigger size, thus a is smaller
else
return lastNumericCompare;
else
return aLength - bLength;
}
It should be fast, without any regular expressions or array manipulation, just a couple of flags and a lot of cases.
This should sort any combination of numbers inside strings and properly support numbers which are equal and move on.
I usually do this by prefixing zeros to the number and handle the whole entity as a string. then sort it.
See this:
public abstract class MyNumberComparator {
protected int doCompare(final String number1, final String number2) {
String strNumber1 = fillUpLeftWithZeros(number1, 30);
String strNumber2 = fillUpLeftWithZeros(number2, 30);
return strNumber1.toUpperCase().compareTo(strNumber2.toUpperCase());
}
}
A simple implementation would be like this one (this works with any string that ends with a number):
public class SplitComparator implements Comparator<String> {
static class Pair implements Comparable<Pair> {
private String name;
private Integer number;
public Pair(String value) {
value = value.trim();
this.name = value.substring( 0, value.lastIndexOf(" ") );
this.number = Integer.valueOf( value.substring( value.lastIndexOf(" ") + 1, value.length() ) );
}
#Override
public int compareTo( Pair right) {
int result = this.name.compareTo( right.name );
if ( result == 0 ) {
result = this.number.compareTo( right.number );
}
return result;
}
}
#Override
public int compare(String left, String right) {
return new Pair( left ).compareTo( new Pair( right ) );
}
public static void main( String ... args ) {
String[] values = { "State Lower Legislative District 1",
"State Lower Legislative District 11",
"State Upper Legislative District 1",
"State Upper Legislative District 11"};
SplitComparator comparator = new SplitComparator();
System.out.println( comparator.compare( values[1] , values[0]) );
System.out.println( comparator.compare( values[0] , values[1]) );
System.out.println( comparator.compare( values[0] , values[3]) );
}
}