Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sort a list of objects based on an unknown number of keys

Tags:

java

java-8

I've seen plenty of ways to sort a list of objects that work fine if you know the incoming keys or at least the incoming number of keys. Problem is in my case I don't know if the user will send in 1 or 10 keys.

Currently I have a giant switch statements for each number of keys, but obviously that scales terribly. It just chains a bunch of 'thenComparing' together.

I found an example here that looks like it kind of helps but I don't know how to build a stream of comparators.

How-to chain and apply a stream of comparators?

Looking for a link or really anything pieces of information that will fill in the gaps on how to do this.

This all works from a user calling webservice where they would call it like

https://host.com/path?sort=[{"attribute1": "ASC"}, {"attribute2": "DESC"}]
like image 917
canpan14 Avatar asked Aug 14 '18 14:08

canpan14


2 Answers

Suppose you have such an entity:

static class Person {
    private final int age;

    private final String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }
}

You could define all the fields and map those to a certain comparator:

Map<String, Comparator<Person>> map = new HashMap<>();
map.put("name_ASC", Comparator.comparing(Person::getName));
map.put("name_DESC", Comparator.comparing(Person::getName).reversed());

map.put("age_ASC", Comparator.comparingInt(Person::getAge));
map.put("age_DESC", Comparator.comparingInt(Person::getAge).reversed());

And then having your input, you could do:

Comparator<Person> all = Stream.of("name_ASC", "age_DESC") // for example
            .map(map::get)
            .reduce(Comparator::thenComparing)
            .orElse((a, b) -> 0); // or whatever you think appropriate

And sorting them after this is obviously a no brainer:

 List<Person> persons = List.of(new Person(20, "Bob"), new Person(30, "Rose"));

 // or Collections.sort(persons, all)
 // persons.sort(all)
 persons.stream().sorted(all).collect(Collectors.toList());
like image 91
Eugene Avatar answered Oct 03 '22 03:10

Eugene


if you can build a list of comparators, you could use something like this:

public class MultiComparator<T> implements Comparator<T> {

    private final List<Comparator<T>> comparators;

    MultiComparator(List<Comparator<T>> comparators){
        this.comparators = comparators;
    }

    @Override
    public int compare(T t1, T t2) {
        int r = 0;
        for(Comparator c : comparators){
            r = c.compare(t1,t2);
            if(r != 0){
                return r;
            }
        }
        return r;
    }

}

(not very java 8 ish though)

like image 27
user1373164 Avatar answered Oct 03 '22 03:10

user1373164