Reputation: 674
I want to sort the List on name property
And the sort should be in ascending like A, a, B, b ... in java
It would be great if someone helps me in this.
For example
package corejava.compare;
import java.util.Comparator;
public class FirstNameSorter implements Comparator<Employee>{
@Override
public int compare(Employee o1, Employee o2) {
return o1.getFirstName().compareTo(o2.getFirstName());
}
}
And I have used this comparator as below:
Employee e1 = new Employee(1, "aTestName", "dLastName", 34);
Employee e2 = new Employee(2, "A", "pLastName", 30);
Employee e3 = new Employee(3, "T", "sLastName", 31);
Employee e4 = new Employee(4, "D", "zLastName", 25);
Employee e5 = new Employee(5, "b", "zLastName", 25);
List<Employee> employees = new ArrayList<Employee>();
employees.add(e2);
employees.add(e3);
employees.add(e1);
employees.add(e4);
employees.add(e5);
Collections.sort(employees, new FirstNameSorter());
// Sorted by firstName
System.out.println(employees);
then I got the following output:
A
D
T
b
But, I want the following output:
A
b
D
T
Upvotes: 1
Views: 617
Reputation: 124265
Problem:
Original solution using only caseInsensitiveThenNaturalOrder
would order a
and Aa
as [a, Aa]
instead of expected [Aa, a]
.
Cause:
String.CASE_INSENSITIVE_ORDER
only considers as equal strings with same length like a
and A
OR aA
and Aa
, which is condition for later provided by thenComparing
comparator representing lexical order to work. But since a
and Aa
have different length they will not be additionally sorted by that lexical order which would place Aa
before a
.
Solution:
To fix this problem we can create Comparator which will manually iterate over "common" indexes of two strings and compare their corresponding characters using created earlier caseInsensitiveThenNaturalOrde
(from original answer below). By comparing single characters we are sure that these will have same length which should solve the problem and let us apply order A < a < B < b < C < c < ...
:
Comparator<String> myComparator = (s1, s2) -> {
for (int i = 0; i < Math.min(s1.length(), s2.length()); i++) {
int compare = caseInsensitiveThenNaturalOrder
.compare(s1.substring(i, i + 1), s2.substring(i, i + 1));
if (compare != 0) return compare;
}
return Integer.compare(s1.length(), s2.length());
};
//DEMO:
String[] arr = {"a","A","aA","Aa","a","aA","bb","b","Bb","bB","aB"};
Arrays.sort(arr, myComparator);
System.out.println(Arrays.toString(arr));
//Output:
//[A, Aa, a, a, aA, aA, aB, Bb, b, bB, bb]
For English only characters you can use Comparator
Comparator<String> caseInsensitiveThenNaturalOrder = String
.CASE_INSENSITIVE_ORDER
.thenComparing(Comparator.naturalOrder());
which first sorts words regardless of their case like b,c,a,A
-> a,A,b,c
. .thenComparing
provides way to order elements considered as equal like a
and A
in a way where A
will be placed before a
(which is natural ordering of string elements)
But if names can hold non-English characters (like in Polish Ą,ą
which should be placed between A,a
and B,b
) we can't use String.CASE_INSENSITIVE_ORDER
because it would place non-English characters after English ones. For case like this we need solution described at Sort List of Strings with Localization like
Collator coll = Collator.getInstance(new Locale("pl", "PL")); //set locale of supported characters
coll.setStrength(Collator.PRIMARY);
Comparator<Object> caseInsensitiveThenNaturalOrder = coll
.thenComparing((o1, o2) -> o1.toString().compareTo(o2.toString()));
Demo (with support of Polish characters):
String[] arr = {"cC","Cc","cc","CC", "ab","aB","Ab","AB", "ą", "Ą"};
Arrays.sort(arr, caseInsensitiveThenNaturalOrder);
System.out.println(Arrays.toString(arr));
Output: [AB, Ab, aB, ab, Ą, ą, CC, Cc, cC, cc]
If you want to sort collection of Employee
instances based on property accessed via getFirstName
you can use caseInsensitiveThenNaturalOrder
Comparator like
Collections.sort(employees, Comparator.comparing(Employee::getFirstName, caseInsensitiveThenNaturalOrder));
which can also be written as
employees.sort(Comparator.comparing(Employee::getFirstName, caseInsensitiveThenNaturalOrder));
Upvotes: 1
Reputation: 883
Maybe this works for you:
public class Main {
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "D", "a", "b", "B", "T");
list.sort(comparator());
System.out.println(list);
}
private static Comparator<? super String> comparator() {
return (Comparator<String>) (leftString, rightString) -> {
if (leftString.length() == rightString.length()) {
for (int index = 0; index < leftString.length(); index++) {
final String leftChar = leftString.substring(index, index + 1);
final String rightChar = rightString.substring(index, index + 1);
if (!leftChar.equals(rightChar)) {
if (leftChar.toUpperCase().equals(rightChar.toUpperCase())) {
if (leftChar.equals(leftChar.toLowerCase()) &&
rightChar.equals(rightChar.toUpperCase())) {
return 1;
} else {
return -1;
}
} else {
return leftChar.toUpperCase()
.compareTo(rightChar.toUpperCase());
}
}
}
return 0;
} else {
return (leftString.length() > rightString.length()) ? -1 : +1;
}
};
}
}
Output:
[A, a, B, b, D, T]
Upvotes: 0
Reputation: 10945
Like @Zabuza mentioned in a comment, the ordering you want is almost case-insensitive alphabetical, but not quite, since you want upper & lowercase characters to be grouped together when they're the same letter.
You can accomplish this by adding simply lower-casing the first names and comparing them, with a separate check to revert to a pure lexicographical ordering if the lower-cased check shows a match:
e.g.
public class FirstNameSorter implements Comparator<Employee> {
@Override
public int compare(Employee o1, Employee o2) {
int precheck = o1.getFirstName().toLowerCase().compareTo(o2.getFirstName().toLowerCase());
return precheck != 0 ? precheck : o1.getFirstName().compareTo(o2.getFirstName());
}
}
Upvotes: 2