Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force max to return ALL maximum values in a Java Stream?

I've tested a bit the max function on Java 8 lambdas and streams, and it seems that in case max is executed, even if more than one object compares to 0, it returns an arbitrary element within the tied candidates without further consideration.

Is there an evident trick or function for such a max expected behavior, so that all max values are returned? I don't see anything in the API but I am sure it must exist something better than comparing manually.

For instance:

// myComparator is an IntegerComparator Stream.of(1, 3, 5, 3, 2, 3, 5)     .max(myComparator)     .forEach(System.out::println); // Would print 5, 5 in any order. 
like image 448
Whimusical Avatar asked Mar 29 '15 20:03

Whimusical


People also ask

How do you find the maximum element of a stream?

Stream. max() returns the maximum element of the stream based on the provided Comparator. A Comparator is a comparison function, which imposes a total ordering on some collection of objects. max() is a terminal operation which combines stream elements and returns a summary result.

Can Java streams be of infinite size?

We can create an infinite stream of any custom type elements by passing a function of a Supplier interface to a generate() method on a Stream.

Do streams have limited storage in Java?

No storage. Streams don't have storage for values; they carry values from a source (which could be a data structure, a generating function, an I/O channel, etc) through a pipeline of computational steps.

What is the purpose of limit method of stream in Java 8?

The limit method of the Stream class introduced in Java 8 allows the developer to limit the number of elements that will be extracted from a stream. The limit method is useful in those applications where the user wishes to process only the initial elements that occur in the stream.


1 Answers

I believe the OP is using a Comparator to partition the input into equivalence classes, and the desired result is a list of members of the equivalence class that is the maximum according to that Comparator.

Unfortunately, using int values as a sample problem is a terrible example. All equal int values are fungible, so there is no notion of preserving the ordering of equivalent values. Perhaps a better example is using string lengths, where the desired result is to return a list of strings from an input that all have the longest length within that input.

I don't know of any way to do this without storing at least partial results in a collection.

Given an input collection, say

List<String> list = ... ; 

...it's simple enough to do this in two passes, the first to get the longest length, and the second to filter the strings that have that length:

int longest = list.stream()                   .mapToInt(String::length)                   .max()                   .orElse(-1);  List<String> result = list.stream()                           .filter(s -> s.length() == longest)                           .collect(toList()); 

If the input is a stream, which cannot be traversed more than once, it is possible to compute the result in only a single pass using a collector. Writing such a collector isn't difficult, but it is a bit tedious as there are several cases to be handled. A helper function that generates such a collector, given a Comparator, is as follows:

static <T> Collector<T,?,List<T>> maxList(Comparator<? super T> comp) {     return Collector.of(         ArrayList::new,         (list, t) -> {             int c;             if (list.isEmpty() || (c = comp.compare(t, list.get(0))) == 0) {                 list.add(t);             } else if (c > 0) {                 list.clear();                 list.add(t);             }         },         (list1, list2) -> {             if (list1.isEmpty()) {                 return list2;             }              if (list2.isEmpty()) {                 return list1;             }             int r = comp.compare(list1.get(0), list2.get(0));             if (r < 0) {                 return list2;             } else if (r > 0) {                 return list1;             } else {                 list1.addAll(list2);                 return list1;             }         }); } 

This stores intermediate results in an ArrayList. The invariant is that all elements within any such list are equivalent in terms of the Comparator. When adding an element, if it's less than the elements in the list, it's ignored; if it's equal, it's added; and if it's greater, the list is emptied and the new element is added. Merging isn't too difficult either: the list with the greater elements is returned, but if their elements are equal the lists are appended.

Given an input stream, this is pretty easy to use:

Stream<String> input = ... ;  List<String> result = input.collect(maxList(comparing(String::length))); 
like image 79
Stuart Marks Avatar answered Sep 24 '22 16:09

Stuart Marks