Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Streams: How to do an efficient "distinct and sort"?

Let's assume I've got a Stream<T> and want to get only distinct elements and sorted.

The naïve approach would be to do just the following:

Stream.of(...)     .sorted()     .distinct() 

or, maybe the other way around:

Stream.of(...)     .distinct()     .sorted() 

Since the implementation of both of them is not really accessible by the JDK's source code I was just wondering about possible memory consumption and performance implications.

Or would it be even more efficient to write my own filter as the following?

Stream.of(...)     .sorted()     .filter(noAdjacentDuplicatesFilter())  public static Predicate<Object> noAdjacentDuplicatesFilter() {     final Object[] previousValue = {new Object()};      return value -> {         final boolean takeValue = !Objects.equals(previousValue[0], value);         previousValue[0] = value;         return takeValue;     }; } 
like image 880
Michaela Maura Elschner Avatar asked May 05 '17 13:05

Michaela Maura Elschner


People also ask

Can you sort a stream Java?

Stream sorted() in Java Stream sorted() returns a stream consisting of the elements of this stream, sorted according to natural order. For ordered streams, the sort method is stable but for unordered streams, no stability is guaranteed.

What is the most efficient way of sorting a list in Java 8?

Fastest = Collections. sort ; Most Readable = Stream ; Most memory efficient = Collections.

How do I sort a list in streams?

Sorting a List of Integers with Stream. Found within the Stream interface, the sorted() method has two overloaded variations that we'll be looking into. This methods returns a stream consisting of the elements of the stream, sorted according to natural order - the ordering provided by the JVM.

How do I get unique values from a stream?

distinct() returns a stream consisting of distinct elements in a stream. distinct() is the method of Stream interface. This method uses hashCode() and equals() methods to get distinct elements. In case of ordered streams, the selection of distinct elements is stable.


1 Answers

When you chain a distinct() operation after sorted(), the implementation will utilize the sorted nature of the data and avoid building an internal HashSet, which can be demonstrated by the following program

public class DistinctAndSort {     static int COMPARE, EQUALS, HASHCODE;     static class Tracker implements Comparable<Tracker> {         static int SERIAL;         int id;         Tracker() {             id=SERIAL++/2;         }         public int compareTo(Tracker o) {             COMPARE++;             return Integer.compare(id, o.id);         }         public int hashCode() {             HASHCODE++;             return id;         }         public boolean equals(Object obj) {             EQUALS++;             return super.equals(obj);         }     }     public static void main(String[] args) {         System.out.println("adjacent sorted() and distinct()");         Stream.generate(Tracker::new).limit(100)               .sorted().distinct()               .forEachOrdered(o -> {});         System.out.printf("compareTo: %d, EQUALS: %d, HASHCODE: %d%n",                           COMPARE, EQUALS, HASHCODE);         COMPARE=EQUALS=HASHCODE=0;         System.out.println("now with intermediate operation");         Stream.generate(Tracker::new).limit(100)             .sorted().map(x -> x).distinct()             .forEachOrdered(o -> {});         System.out.printf("compareTo: %d, EQUALS: %d, HASHCODE: %d%n",                           COMPARE, EQUALS, HASHCODE);     } } 

which will print

adjacent sorted() and distinct() compareTo: 99, EQUALS: 99, HASHCODE: 0 now with intermediate operation compareTo: 99, EQUALS: 100, HASHCODE: 200 

The intermediate operation, as simple as map(x -> x), can’t be recognized by the Stream implementation, hence, it must assume that the elements might not be sorted in respect to the mapping function’s result.

There is no guaranty that this kind of optimization happens, however, it is reasonable to assume that the developers of the Stream implementation will not remove that optimization and even try to add more optimizations, so rolling your own implementation will prevent your code from benefiting from future optimizations.

Further, what you have created is a “stateful predicate”, which is strongly discouraged, and, of course, will break when being used with a parallel stream.

If you don’t trust the Stream API to perform this operation efficiently enough, you might be better off implementing this particular operation without the Stream API.

like image 174
Holger Avatar answered Sep 22 '22 01:09

Holger