Strobe_
Strobe_

Reputation: 515

Java - Sort a list of objects that includes a special character

Basically i have an arraylist of objects like so:

[{name: 'A'}, {name: 'B'}, {name:'?'}]

I want to sort these so that the question mark is at the end like above.

But using the below code.

Collections.sort(myList);

This always results in the object with the question mark first, I think this is due to ASCII ordering? I think the correct way forward is to use a comparator function but i'm not sure how that would take shape with letters and special characters?

How would I implement this?

Upvotes: 2

Views: 1804

Answers (3)

andrewJames
andrewJames

Reputation: 21918

An alternative approach - one which sorts any punctuation to the end - can be achieved using a rules-based collator.

An example:

List<String> words = Arrays.asList(
        "?dog", "rat", "456", "123", "dog", "pig", "?cat", "!rat", "cat"
);
String englishRules = ("< 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 < w,W < x,X "
        + "< y,Y < z,Z < 0,1,2,3,4,5,6,7,8,9");

RuleBasedCollator rbc = new RuleBasedCollator(englishRules);
rbc.setStrength(Collator.PRIMARY);

Collections.sort(words, rbc);
words.forEach((word) -> {
    out.print(word + " ");
});

This outputs:

cat dog pig rat 123 456 !rat ?cat ?dog 

Points to note:

1) This specific example is limited to an English collation.

2) The general technique works because all non-mentioned characters are sorted to the end. So, not only are punctuation symbols sorted after English letters and digits - but so is every other character/symbol (e.g. those used by other scripts).

3) If you want a non-Unicode ordering of punctuation symbols, they need to be surrounded by single quotes in your rule string:

"... < 0,1,2,3,4,5,6,7,8,9 < '?' < '!'"

Upvotes: 2

Klitos Kyriacou
Klitos Kyriacou

Reputation: 11621

You can use some useful methods from java.util.Comparator to make your life easier and your code less error-prone than having to think about if-else or ternary operators:

class MyObj {
    private String name;

    MyObj(String name) {
        this.name = name;
    }

    String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "{name: '" + name + "'}";
    }
}

public class Demo {
    public static void main(String[] args) {
        List<MyObj> lst = Arrays.asList(new MyObj("B"), new MyObj("?"), new MyObj("A"));
        Comparator<String> questionMarksLast = Comparator
                .<String, Boolean>comparing("?"::equals)
                .thenComparing(Comparator.naturalOrder());

        lst.sort(Comparator.comparing(MyObj::getName, questionMarksLast));
        System.out.println(lst);  // prints [{name: 'A'}, {name: 'B'}, {name: '?'}]
    }
}

Upvotes: 1

Tim Biegeleisen
Tim Biegeleisen

Reputation: 521429

In Java 8, you may use a two-tiered custom comparator:

// given
List<YourObject> list;
list.sort((o1, o2) -> "?".equals(o1.getName()) ? 1 :
    ("?".equals(o2.getName()) ? -1 : o1.getName().compareTo(o2.getName())));

The sorting logic here is that if one or the other names be ?, then we always sort that ? last. If both names be ?, or if neither be ?, then we sort using the default lexicographical string sorting.

Upvotes: 1

Related Questions