Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stream doesn't preserve the order after grouping

I have a List name availableSeats I am sorting and grouping by the blockIndex property like below:

availableSeats.stream()
                .sorted(Comparator.comparing(SeatedTicketAssignment::getBlockIndex))
                .collect(Collectors.groupingBy(SeatedTicketAssignment::getBlockIndex))
                .forEach((block, blockAssignments) -> {
                     //Rest of the code
                } 

The problem is that the result of grouping by is not sorted by the blockIndex.

like image 285
Milad Avatar asked Aug 07 '17 23:08

Milad


People also ask

Do streams preserve order?

If the stream is ordered, the encounter order is preserved. It means that the element occurring first will be present in the distinct elements stream. If the stream is unordered, then the resulting stream elements can be in any order. Stream distinct() is a stateful intermediate operation.

Does arrays stream preserve order?

If our Stream is ordered, it doesn't matter whether our data is being processed sequentially or in parallel; the implementation will maintain the encounter order of the Stream.

Does Java stream Flatmap preserve order?

Does flatmap() method preserve the order of the streams? Yes, It does and map() also.

Does Java stream distinct preserve order?

The distinct() method guarantees the ordering for the streams backed by an ordered collection. For ordered streams, the element appearing first in the encounter order is preserved.


Video Answer


3 Answers

Keep in mind, Collectors#groupingBy(Function) will return a HashMap, which does not guarantee order. If you want the ordering to be in the order that the aggregate identity (your i % 2 == 0 result) shows up, then you can use LinkedHashMap:

.collect(Collectors.groupingBy(i -> i % 2 == 0, LinkedHashMap::new, Collectors.toList()))

Would return a LinkedHashMap<Boolean, List<SeatedTicketAssignment>> (since your collector is grouped by a boolean). Additionally, since the list utilized by the collector is an ArrayList, it should preserve the iteration order of the stream relative to the list.

like image 183
Rogue Avatar answered Oct 19 '22 23:10

Rogue


Unfortunately Stream API implementation is not aware of the fact, that the stream you pass is already sorted by what you need so "grouping" is actually trivial. Thus it uses the default way which is essentially similar to this SO answer i.e. create a Map for groups and fill it with elements of the stream. And by default the Map implementation that is used is the HashMap (see code here) which is good for performance reasons but bad for your goal because HashMap doesn't preserve the order of the keys than first sorting.

It might seems a bit unlucky that Group By is implemented in Stream API only as a "collector" so you can't first group and then sort in a one-liner. But this seems intentional: there is no way to implement Group By without fully materializing result so it can't be lazy and thus has to be a collector. @Rogue provided a nice trick with LinkedHashMap but to me it is to bound to implementation details. Still I would write a few more lines of code and first group and then sort the entries of the list (i.e. actually grouped HashMap) by key. Most probably it would be faster.

like image 30
SergGr Avatar answered Oct 19 '22 21:10

SergGr


Since the groupingBy collector does not require sorted input, you can sort the groups after collection. This will be faster than sorting items first, anyway, assuming that there are fewer groups than items:

availableSeats.stream()
        .collect(Collectors.groupingBy(SeatedTicketAssignment::getBlockIndex))
        .entrySet().stream()
        .sorted(Comparator.comparing(Map.Entry::getKey))
        .forEach(mapEntry -> {
             //Rest of the code
        } 
like image 2
Matt Timmermans Avatar answered Oct 19 '22 21:10

Matt Timmermans