Search code examples
javajava-stream

How to conditionally reverse the order of a stream where I'm using comparators to sort initially?


I have code as such, which selects and chains comparators:

class InputObject {  String inputName;  String ascending; }

Map<String, Comparator> comparatorsMap = .... // Used to dynamically pull comparators for use

List<InputObject> input = Arrays.asList(new InputObject("hello", true), new InputObject("goodbye", false));

Comparator<OutputObject> comparator = input.stream()
        .map(comparatorsMap::get)
        .reduce(Comparator::thenComparing)
        .orElse((a, b) -> 0);

List<OutputObject> sorted = dataCollection.stream().sorted( comparator ).collect( Collectors.toList() );

I'd like to use the ascending field from InputObject as a conditional to determine if it should reverse the collection, but I'm not sure how to achieve that. Note that each input object has its own ascending field, so these would have to be intermediate actions.

So example:

Unsorted (input stream is [ {"byName", false}, {"byID", true} ] ):

OutputObject=("Mike", 5)
OutputObject=("Bob", 4)
OutputObject=("Mike", 1)

After first sort (on name):

OutputObject=("Bob", 4)
OutputObject=("Mike", 5)
OutputObject=("Mike", 1)

Then reverse (since ascending == false)

OutputObject=("Mike", 5)
OutputObject=("Mike", 1)
OutputObject=("Bob", 4)

Then after applying the second sort (by ID if tie, and ascending == true)

OutputObject=("Mike", 1)
OutputObject=("Mike", 5)
OutputObject=("Bob", 4)

I thought about potentially saving a boolean field within the map lambda and leveraging that in a different lambda, but I'm not sure how you can apply the action of reversing the stream / collection conditionally.


Solution

  • I think you're looking for something like this:

    Comparator<OutputObject> comparator = input.stream()
            .map(in -> {
              Comparator<OutputObject> c = comparatorsMap.get(in.inputName);
              return in.ascending ? c : c.reversed();
            })
            .reduce(Comparator::thenComparing)
            .orElse((a, b) -> 0);
    

    This conditionally reverses the comparator from comparatorsMap, based on the ascending flag.

    I thought about potentially saving a boolean field within the map lambda and leveraging that in a different lambda

    You could do it like this:

    input.stream()
        .map(in -> new AbstractMap.SimpleEntry<>(comparatorsMap.get(in.inputName), in.ascending))
        .map(e -> e.getValue() ? e.getKey() : e.getKey().reversed())
        ...
    

    but I think that's far less easy to understand.