John Baum
John Baum

Reputation: 3331

Sorting logic configured by string order

I have a comma separated string which holds the sort criteria for sorting a list of Students:

Input:
String sortOrder = "height, weight, age"
List<student> students;

Each of the elements above are Comparators on the student object that sort the Student object with some complex logic. I need to know how best to translate the sort order as it appears in the sortOrder string and activate the matching comparators in that order. So for the above example, the height comparator would run first and the age one last.

Upvotes: 0

Views: 138

Answers (2)

Tunaki
Tunaki

Reputation: 137329

If I understand correctly what you want, you want to sort the list of students by their heights, then their weight and then their age. But you want this list of property to be dynamic.

This means we need to implement a custom Comparator that works with a given property of a given class.

A first implementation could be creating a Map where each String property is mapped to an associate Comparator. This would be a clean approach (see @Manos Nikolaidis' answer, and Misha's comment, for this implementation).

A true dynamic solution is possible using a little bit of reflection: first the declared field having the given name of the given class is retrieved. It is set accessible since this field is most likely private. Finally, a Comparator comparing the value of this field for each student is returned. This code assumes blindly that the target property is in fact Comparable (otherwise, why would we want to compare using this field?).

private static <U> Comparator<U> comparingProperty(String property, Class<U> clazz) {
    try {
        Field field = clazz.getDeclaredField(property);
        field.setAccessible(true);
        return Comparator.comparing(s -> {
            try {
                @SuppressWarnings("unchecked")
                Comparable<Object> comparable = (Comparable<Object>) field.get(s);
                return comparable;
            } catch (IllegalAccessException e) {
                throw new AssertionError(e);
            }
        });
    } catch (NoSuchFieldException e) {
        throw new AssertionError(e);
    }
}

Once that we have this utility comparator, we can sort a list of student easily. A stream of the sort properties is created by splitting around "," and trimming the results. Then, each property is mapped to its corresponding Comparator and, finally, this stream is reduced by combining all of the comparators together with Comparator::thenComparing:

students.sort(Stream.of(sortOrder.split(","))
                    .map(String::trim)
                    .map(s -> comparingProperty(s, Student.class))
                    .reduce(Comparator::thenComparing)
                    .get()
             );

Upvotes: 1

Manos Nikolaidis
Manos Nikolaidis

Reputation: 22264

You can make a HashMap mapping these words to Comparator objects. Then use the Comparators for sorting

Map<String, Comparator<student>> comparators = new HashMap<>();

after you add Comparator objects to comparators like this :

comparators.put("height", Comparator.comparingDouble(student::getHeight));

If you would like to perform different sorts consecutively, just go through the words in the sortOrder and apply e.g.

for (String comp : sortOrder.split(", "))
    Collections.sort(students, comparators.get(comp));

Upvotes: 2

Related Questions