Vishwa Ratna
Vishwa Ratna

Reputation: 6420

Custom Sorting in way that A comes before a and B comes before b

I have a List of colors like this:

Pink, Blue, Red, blue, Grey, green, purple, black ...etc

List<String> listOfColors =  Arrays.asList("Pink", "Blue", "Red", "blue", "Grey", "green", "purple", "black");

There are some intermediate operation like filtering some fruit colors, now I am left with filtered results where I want them to be sorted in order:

Blue, black, blue, Grey, green, Pink, purple, Red

I have tried :

List<String> collect = listOfColors.stream().sorted(String::compareToIgnoreCase)
        .collect(Collectors.toList());

It does not work as expected.

The output is the following:

black, Blue, blue, green, Grey, Pink, purple, Red

I want the following:

Blue, black, blue, Grey, green, Pink, purple, Red

Upvotes: 11

Views: 1410

Answers (3)

dbush
dbush

Reputation: 225352

You need a method that first does a case insensitive comparison on each letter, then if there's a match perform a case sensitive comparison on each letter:

public static int compare(String s1, String s2)
{
    int len, i;
    if (s1.length()<s2.length()) {
        len = s1.length();
    } else {
        len = s2.length();
    }
    for (i=0;i<len;i++) {
        if (Character.toUpperCase(s1.charAt(i)) < Character.toUpperCase(s2.charAt(i))) {
            return -1;
        } else if (Character.toUpperCase(s1.charAt(i)) > Character.toUpperCase(s2.charAt(i))) {
            return 1;
        } else if (s1.charAt(i) < s2.charAt(i)) {
            return -1;
        } else if (s1.charAt(i) > s2.charAt(i)) {
            return 1;
        }
    }
    if (s1.length() < s2.length()) {
        return -1;
    } else if (s1.length() > s2.length()) {
        return 1;
    } else {
        return 0;
    }
}

You can then pass this method to Stream.sorted.

Upvotes: 0

Vishwa Ratna
Vishwa Ratna

Reputation: 6420

You can use RuleBasedCollator to define your own Rules.

Example of custom rule:

String rules = "< c,C < b,B";

The above rule is decoded as that both uppercase and lowercase C's are to appear before both uppercase and lowercase B's when comparing strings.

String customRules = "<A<a<B<b<C<c<D<d<E<e<F<f<G<g<H<h<I<i<J<j<K<k<L<l<M<m<N<n<O<o<P<p<Q<q<R<r<S<s<T<t<U<u<V<v<X<x<Y<y<Z<z";
RuleBasedCollator myRuleBasedCollator = new RuleBasedCollator(customRules);
Collections.sort(listOfColors,myRuleBasedCollator);
System.out.println(listOfColors);

Output:

[Blue, black, blue, Grey, green, Pink, purple, Red]

Edit: Instead of writing the customRules by hand, you can use the below to code to generate it.

String a = IntStream.range('a', 'z' + 1).mapToObj(c -> Character.toString((char) c))
        .flatMap(ch -> Stream
            .of("<", ch.toUpperCase(), "<", ch)).collect(Collectors.joining(""));

Upvotes: 9

DanielBK
DanielBK

Reputation: 952

My solution is to use sorting in two steps by using the Comparator.thenComparing() method.

First, compare the Strings only by the first character ignoring case. So the groups with the same first character (no matter what case) remain unsorted so far. Then in the second step apply the normal alphabetical sorting to sort those unsorted subgroups.

List<String> listOfColors =  Arrays.asList("Pink", "Blue", "Red", "blue", "Grey", "green", "purple", "black");
Comparator<String> comparator = Comparator.comparing(s -> 
        Character.toLowerCase(s.charAt(0)));
listOfColors.sort(comparator.thenComparing(Comparator.naturalOrder()));
System.out.println(listOfColors);

Maybe it can be still optimized, but it gives the desired result:

[Blue, black, blue, Grey, green, Pink, purple, Red]

Upvotes: 8

Related Questions