2

I am trying to sort a list based on sort key and sort order I receive from an API. For example, I have a list with sortkey and sortorder and based on that I need to sort.

List<SortList> sortlist;

I have a list of an object :

List<Employee> employee;

I am able to sort using

Collections.sort(sourceList, Comparator
                .comparing(Employee::getAge).reversed()
                .thenComparing(Employee::getCount));

But i need to check the sortfeild on a condition and based on that only the field is considered for sorting.

ex:

if(sortkey = "name") sortbythatkey from sortlist by the sort order

if (sortkey = "place") sortbythat key from sortlist by the sort order

So here if sortlist has both name and place then it should sort by both key and order

Any idea how could i achieve this?

Sort List contains:

{
    "sortKey":"name",
    "sortOrder":"ASC"

},
{
    "sortKey":"place",
    "sortOrder":"DESC"

}

Requirement is to chain them together like ORDER BY in SQL

2 Answers2

1

You can create a method which when passed the sort key, you provide the proper Comparator:

public Comparator<Employee> getComparator(String sortKey) {
    if("name".equals(sortKey)) {
        return Comparator.comparing(Employee::getName);
    } else if ("place".equals(sortKey) {
        return Comparator.comparing(Employee::getPlace);
    } else {
        throw new IllegalArgumentException();
    }
}

To call it it would simply be:

Collections.sort(sourceList, getComparator(sortKey).reversed()
                .thenComparing(Employee::getCount));

While you could also write your own, I find it is better to delegate the "standard" parts and simply write the part that differs from this.

If you find yourself having many such sort keys, then a more suitable means to do this would be to use a map:

private static final Map<String, Comparator<Employee>> COMPARE_MAP = new HashMap<>() {{
    put.("name", Comparator.comparing(Employee::getName));
    put.("place", Comparator.comparing(Employee::getPlace));
}});

public Comparator<Employee> getComparator(String sortKey) {
    if(COMPARE_MAP.containsKey(sortKey)) {
        return COMPARE_MAP.get(sortKey);
    } else {
        throw new IllegalArgumentException();
    }
}

Reflection is also an option, but I would be cautious to use reflection unless it becomes impractical to do otherwise. In that case, you could create your own annotation to determine which fields of class Employee can be used for sorting.

Neil
  • 5,762
  • 24
  • 36
  • Here how could I determine the sort order, sort order from request may contain DESC and ASC, so how to sort based on that by this? also will it work like chaining them together? – Karthik Cherukunnumal Jul 25 '19 at 07:39
  • 1
    It's much cleaner to use a `Map` for this sort of thing. – chrylis -cautiouslyoptimistic- Jul 25 '19 at 07:41
  • @chrylis True, in fact if I had to add just one more here, I would most certainly use a map. I'll adjust my answer accordingly. – Neil Jul 25 '19 at 07:49
  • @Neil : here how could i add sortOrder, as i also check need to check the sortOrder and sort accordingly – Karthik Cherukunnumal Jul 25 '19 at 08:02
  • @KarthikCherukunnumal If you want it descending, you call ".reversed()" on the resulting Comparator returned. You could also pass a boolean to the method if you prefer. – Neil Jul 25 '19 at 08:11
  • @Neil Will this method work like chaining, i guess this will sort based on name field first and when sort with place again sort with place feild without considering name right? My puporse is like ORDER BY in sql – Karthik Cherukunnumal Jul 25 '19 at 09:35
  • @KarthikCherukunnumal You can chain by calling `.thenComparing` and then performing subsequent calls to getComparator with different sort keys. Or you could also create a method which will do this automatically given a variable number of sort keys. – Neil Jul 25 '19 at 10:23
  • 1
    Using a `Map` is a clean approach, creating a subclass of `HashMap`, just for the sake of saving a few characters in source code, is not. Besides, `put.(` is not even valid syntax. – Holger Jul 25 '19 at 11:58
1

Assuming that sortlist is a list of SortCriteria, which is a class like this:

class SortCritera {
    private String key;
    private String order;

    public String getKey() {
        return key;
    }

    public String getOrder() {
        return order;
    }

    // constructors, setters...
}

You first need a HashMap<String, Comparator<Employee>> to store all the corresponding comparators for each possible key:

HashMap<String, Comparator<Employee>> comparators = new HashMap<>();
comparators.put("name", Comparator.comparing(Employee::getName));
comparators.put("age", Comparator.comparing(Employee::getAge));
// ...

Then you can loop through the sortlist and keep calling thenComparing:

Comparator<Employee> comparator = comparators.get(sortlist.get(0).getKey());
if (sortlist.get(0).getOrder().equals("DESC")) {
    comparator = comparator.reversed();
}
for(int i = 1 ; i < sortlist.size() ; i++) {
    if (sortlist.get(i).getOrder().equals("DESC")) {
        comparator = comparator.thenComparing(comparators.get(sortlist.get(i).getKey()).reversed());
    } else {
        comparator = comparator.thenComparing(comparators.get(sortlist.get(i).getKey()));
    }
}
// now you can sort with "comparator".

As Holger has suggested, you can use the Stream API to do this as well:

sortlist.stream().map(sc -> {
    Comparator<Employee> c = comparators.get(sc.getKey()); 
    return sc.getOrder().equals("DESC")? c.reversed(): c; 
}).reduce(Comparator::thenComparing)
.ifPresent(x -> Collections.sort(originalList, x));
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • Why 1st get(0) case is given outside the for loop? and it is not checking for ASC case right? – Karthik Cherukunnumal Jul 25 '19 at 10:20
  • @KarthikCherukunnumal The first case is special because you need to call `comparing` instead of `thenComparing`. I am assuming that if it is not descending, it is ascending. – Sweeper Jul 25 '19 at 10:21
  • ok, but for second case we are thenComparing with Comparator.comparing(Employee::getName) right? will that work? – Karthik Cherukunnumal Jul 25 '19 at 10:26
  • @KarthikCherukunnumal Why won't it? `thenComparing` has an overload that accepts a `Comparator`. – Sweeper Jul 25 '19 at 10:27
  • I tried this, but unfortunately sorting is not working. – Karthik Cherukunnumal Jul 25 '19 at 10:28
  • @KarthikCherukunnumal Can you show how is it not working, by providing the original list, the `sortlist` and the result you got? – Sweeper Jul 25 '19 at 10:30
  • [{"sort_key":"inventory_quantity_in_stock_room","sort_order":"DESC"},{"sort_key":"view_pattern_length_code","sort_order":"ASC"}] Above is sort list { "cost_sequence_number": "9" "inventory_quantity_in_stock_room": 12375.6560, }, { "cost_sequence_number": "9", "inventory_quantity_in_stock_room": 20.0000, }, { "cost_sequence_number": "9", "inventory_quantity_in_stock_room": 24690.4650, } inventory_quantity_in_stock_room is BigDecimal – Karthik Cherukunnumal Jul 25 '19 at 10:34
  • Same result before and after sorting is applied – Karthik Cherukunnumal Jul 25 '19 at 10:37
  • I think COlelction.sort(sorcelist, comparator) need to be called – Karthik Cherukunnumal Jul 25 '19 at 10:39
  • @KarthikCherukunnumal Did you not call it? – Sweeper Jul 25 '19 at 10:39
  • Yes, now called it and worked. Need to add more data and check whether chaining is working, Thanks – Karthik Cherukunnumal Jul 25 '19 at 10:43
  • @KarthikCherukunnumal What do you mean by chaining? – Sweeper Jul 25 '19 at 10:44
  • What i mean is LIKE ORDER BY in SQL, For example, the first sort based on one key and keeping that sorted list, again sort based on another key where the second feild sorting is done without altering the previous sorted order, so that list will be sorted by both keys together. I guess this will work such a way – Karthik Cherukunnumal Jul 25 '19 at 10:46
  • @KarthikCherukunnumal if I understand correctly, that's exactly what `thenComparing` does. – Sweeper Jul 25 '19 at 10:47
  • Yes, i understand, just want to confirm with more data, anyway thanks – Karthik Cherukunnumal Jul 25 '19 at 10:48
  • 1
    Lots of code duplication inside the loop body. How about another local variable? `Comparator next = comparators.get(sortlist.get(i).getKey()); if(sortlist.get(i).getOrder().equals("DESC")) { next = comparator.reversed(); } comparator = comparator.thenComparing(next);` Or you use the Stream API with Reduction: `sortlist.stream() .map(sc -> { Comparator c = comparators.get(sc.getKey()); return sc.getOrder().equals("DESC")? c.reversed(): c; }) .reduce(Comparator::thenComparing) .ifPresent(theList::sort);` – Holger Jul 25 '19 at 11:13
  • 1
    Instead of `x -> Collections.sort(originalList, x)` you can also use `x -> originalList.sort(x)`, which is equivalent to `originalList::sort`… – Holger Jul 25 '19 at 11:53
  • @Holger what if one of the field is null? How could we handle the null case here? .I.e if age is null – Karthik Cherukunnumal Aug 06 '19 at 17:25
  • @KarthikCherukunnumal See https://stackoverflow.com/questions/28499846/null-safe-mapping-comparator-using-default-implementations – Sweeper Aug 07 '19 at 01:23
  • @Sweeper yes I found that.. but how could I implement that here? As we are dynamically using ASC and DESC based on sort key. do we need to add that in our Map? Could you please provide a sample here – Karthik Cherukunnumal Aug 07 '19 at 02:10
  • @KarthikCherukunnumal I haven't tried this, but I _think_ you can just call `Comparator.nullsFirst` or `Comparator.nullsLast` on the `comparator` after the for loop, or `x` if you are using the streams approach. – Sweeper Aug 07 '19 at 02:12
  • @KarthikCherukunnumal you will put the null-safe comparators into the map, i.e. instead of `comparators.put("age", Comparator.comparing(Employee::getAge));` you write `comparators.put("age", Comparator.comparing(Employee::getAge, Comparator.nullsFirst(Comparator.naturalOrder())));`, which is easy to derive from that linked Q&A. Then, the code dealing with ASC and DESC is not affected at all. – Holger Aug 07 '19 at 06:59
  • @Holger. yes, I have done that and it worked. Thank you. – Karthik Cherukunnumal Aug 08 '19 at 07:03