parthivrshah
parthivrshah

Reputation: 674

sort results in ascending return in the form A, a, B, b... in Java

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

Answers (3)

Pshemo
Pshemo

Reputation: 124265

Update (bugFix):

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]

Original answer

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

mmirwaldt
mmirwaldt

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

azurefrog
azurefrog

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

Related Questions