Java stream collect check if result would contain element - java

As I couldn't find anything related to this, I am wondering if streams even allow this.
In my answer to another question, I have following code to add elements to a result list, only if the result list doesn't already contain it:
List<Entry<List<Integer>, Integer>> list = new ArrayList<>(diffMap.entrySet());
list.sort(Entry.comparingByValue());
List<List<Integer>> resultList = new ArrayList<>();
for (Entry<List<Integer>, Integer> entry2 : list) {
if (!checkResultContainsElement(resultList, entry2.getKey()))
resultList.add(entry2.getKey());
}
checkResultContainsElement method:
private static boolean checkResultContainsElement(List<List<Integer>> resultList, List<Integer> key) {
List<Integer> vals = resultList.stream().flatMap(e -> e.stream().map(e2 -> e2))
.collect(Collectors.toList());
return key.stream().map(e -> e).anyMatch(e -> vals.contains(e));
}
Now I am wondering, if this for-loop:
for (Entry<List<Integer>, Integer> entry2 : list) {
if (!checkResultContainsElement(resultList, entry2.getKey()))
resultList.add(entry2.getKey());
}
can be realized using streams. I don't think that .filter() method would work, as it would remove data from List<Entry<List<Integer>, Integer>> list while I don't even know if an element should be considered. I guess that a custom collector could work, but I also wouldn't know how to implement one, as the result is constantly changing with each newly added element.
I am looking for something like this (can be different if something else is better):
list.stream().sorted(Entry.comparingByValue()).collect(???);
where ??? would filter the data and return it as a list.
The values of one result list may not be contained in another one. So these lists are valid:
[1, 2, 3, 4]
[5, 6, 7, 8]
[12, 12, 12, 12]
but of these, only the first is valid:
[1, 2, 3, 4] <-- valid
[5, 3, 7, 8] <-- invalid: 3 already exists
[12, 12, 2, 12] <-- invalid: 2 already exists

If we put aside for a moment the details on whether implementation will be stream-based or not, the existing implementation of how uniqueness of the values of incoming lists is being checked can be improved.
We can gain a significant performance improvement by maintaining a Set of previously encountered values.
I.e. values from each list that was added to the resulting list would be stored in a set. And in order to ensure uniqueness of every incoming list, its values would be checked against the set.
Since operations of a stream pipeline should be stateless, as well as collector shouldn't hold a state (i.e. changes should happen only inside its mutable container). We can approach this problem by defining a container that will encompass a resulting list of lists of Foo and a set of foo-values.
I've implemented this container as a Java 16 record:
public record FooContainer(Set<Integer> fooValues, List<List<Foo>> foosList) {
public void tryAdd(List<Foo> foos) {
if (!hasValue(foos)) {
foos.forEach(foo -> fooValues.add(foo.getValue()));
foosList.add(foos);
}
}
public boolean hasValue(List<Foo> foos) {
return foos.stream().map(Foo::getValue).anyMatch(fooValues::contains);
}
}
The record shown above would is used as a mutable container of a custom collector created with Colloctors.of(). Collector's accumulator make's use of tryAdd() method defined by the container. And the finisher extracts the resulting list from the container.
Note that this operation is not parallelizable, hence combiner of the collector throws an AssertionError.
public static void main(String[] args) {
Map<List<Foo>, Integer> diffMap =
Map.of(List.of(new Foo(1), new Foo(2), new Foo(3)), 1,
List.of(new Foo(1), new Foo(4), new Foo(5)), 2,
List.of(new Foo(7), new Foo(8), new Foo(9)), 3);
List<List<Foo>> result = diffMap.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.collect(Collector.of(
() -> new FooContainer(new HashSet<>(), new ArrayList<>()),
FooContainer::tryAdd,
(left, right) -> {throw new AssertionError("The operation isn't parallelizable");},
FooContainer::foosList
));
System.out.println(result);
}
Output:
[[Foo{1}, Foo{2}, Foo{3}], [Foo{7}, Foo{8}, Foo{9}]]

May be something like this:-
list.stream().
sorted(Entry.comparingByValue()).
collect(ArrayList<List<Foo>>::new,(x,y)->!checkResultContainsElement(x, y.getKey()),(x,y)->x.add(y.getKey()));

Related

Explaining Java Stream map function using only reduce and lambdas

I am currently working through a Java 8 Lambdas book (quite a popular one) and I am confused about some syntax in the ANSWER for one of the advanced questions.
The question asks the following:
Write an implementation of the Stream function 'map' using only reduce and lambda expressions. You can return a List instead of a Stream.
I'd like to highlight that I am not interested in the "best" answer to this question, I am interested in understanding the syntax of the answer to this question given in the book. The answer is as follows:
public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {
return stream.reduce(new ArrayList<O>(), (acc, x) -> {
// We are copying data from acc to new list instance. It is very inefficient,
// but contract of Stream.reduce method requires that accumulator function does
// not mutate its arguments.
// Stream.collect method could be used to implement more efficient mutable reduction,
// but this exercise asks to use reduce method.
List<O> newAcc = new ArrayList<>(acc);
newAcc.add(mapper.apply(x));
return newAcc;
}, (List<O> left, List<O> right) -> {
// We are copying left to new list to avoid mutating it.
List<O> newLeft = new ArrayList<>(left);
newLeft.addAll(right);
return newLeft;
});
}
I understand what a reduce function does, and thus I understand the instantiation of the initial ArrayList, and the part which follows - creating a new ArrayList, adding the new function to the list to accumulate, and then returning the new ArrayList as the result.
The bit I do not understand is the next part:
, (List<O> left, List<O> right) -> {
// We are copying left to new list to avoid mutating it.
List<O> newLeft = new ArrayList<>(left);
newLeft.addAll(right);
return newLeft;
});
What is this doing? I understand the contents of the lambda i.e. the behaviour. But I don't understand what this lambda is doing in the entire context of the reduce function? Why wasn't the first section enough? Why do we have this extra lambda here and how is it contributing to this map function that we are creating?
So far Java 8 lambdas have been pretty straightforward, I feel as though I understood all the theory in the book so far, but maybe I misunderstood something? I wonder what I am missing here?
This last part is called a combiner and is useful if your Stream is parallel.
It will create multiple intermediate results that will need to be put together in the end.
This is exactly what this lambda is doing.
You can this by executing the following piece of code first, which will run a sequential Stream through your function. Notice how I added a System.out.println("Combining...") inside of the combiner.
public static void main(String[] args) {
Stream<Integer> boxed = IntStream.rangeClosed(1, 10).limit(25).boxed();
List<String> map = map(boxed, String::valueOf);
System.out.println(map);
}
public static <I, O> List<O> map(Stream<I> stream, Function<I, O> mapper) {
return stream.reduce(new ArrayList<O>(), (acc, x) -> {
// We are copying data from acc to new list instance. It is very inefficient,
// but contract of Stream.reduce method requires that accumulator function does
// not mutate its arguments.
// Stream.collect method could be used to implement more efficient mutable reduction,
// but this exercise asks to use reduce method.
List<O> newAcc = new ArrayList<>(acc);
newAcc.add(mapper.apply(x));
return newAcc;
}, (List<O> left, List<O> right) -> {
System.out.println("Combining...");
// We are copying left to new list to avoid mutating it.
List<O> newLeft = new ArrayList<>(left);
newLeft.addAll(right);
return newLeft;
});
}
Prints
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Now run the following part, where I made the Stream parallel
public static void main(String[] args) {
Stream<Integer> boxed = IntStream.rangeClosed(1, 10).parallel().limit(25).boxed();
List<String> map = map(boxed, String::valueOf);
System.out.println(map);
}
It prints
Combining...
Combining...
Combining...
Combining...
Combining...
Combining...
Combining...
Combining...
Combining...
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
More info about combiners

Java 8 - Stream with Objects and primitive wrappers

I am working with java 8 stream and trying to modify the object content in the forEach terminal operation.
The issues which i am facing here is that i am able to modify the List<Employee> object contents but not able to modify the contents of List<Integer>
The code snippet is as follows:
public static void streamExample() {
List<Employee> listEmp = Arrays.asList(new Employee());
listEmp.stream().forEach(a -> a.setEmptName("John Doe"));
listEmp.stream().forEach(System.out::println);
List<Integer> listInteger = Arrays.asList(2, 4, 6, 8, 12,17, 1234);
listInteger.stream().filter(v -> v % 2 == 0).forEach(a -> a=a+1);
listInteger.stream().forEach(System.out::println);
}
I am wondering the change is not reflecting back in the list because of unboxing the Integer object while performing the a=a+1 operation but not sure.
You use not optimal approach of Stream. Do think of each step in Stream as modify existed (or create new) element and return it back to the Stream. Finally you receive final result and you can use one of final method to finalize (and actually run the whole stream working) the Stream:
List<Integer> listInteger = Arrays.asList(2, 4, 6, 8, 12, 17, 1234);
listInteger.stream().filter(v -> v % 2 == 0).forEach(a -> a = a + 1);
listInteger.stream().forEach(System.out::println);
Here you have initial array. You want to do following:
Filter out some elements (this is not final step);
Print filtered elements out (this is final step).
To do so, you do not have to create Streams two times. Do use one:
Stream.of(2, 4, 6, 8, 12, 17, 1234) // create stream (there're many way to do it)
.filter(v -> v % 2 == 0) // filter out required elements
.map(v -> v + 1) // transform elements using given rule
.forEach(System.out::println); // finalize stream with printing out each element separately
Note: Stream.of(...) creates a Stream, then we add two steps to the stream filter and map and then finalize or START created stream with forEach.
You are assigning a new value to a local variable (a), so that has no affect on the source of the second Stream (your List<Integer>). Note that this is not what you are doing with your List<Employee>, where you are calling a setter method to mutate the elements of the List.
Since Integers are immutable, you can't mutate the elements of your input List<Integer>.
Instead, you can create a new List:
List<Integer> newList =
listInteger.stream()
.map(v -> v % 2 == 0 ? v + 1 : v)
.collect(Collectors.toList());
Or you can stream over the indices of your List, and replace some of the elements of that List:
IntStream.range(0,listInteger.size())
.filter(i -> listInteger.get(i) % 2 == 0)
.forEach(i -> listInteger.set(i, listInteger.get(i + 1));

Convert a set to map in java

I have a set and I want to convert it to map in order to use it later in guava's Maps.difference(). I only care about the keys in the difference.
Came up with this version:
private <T> Map<T, T> toMap(Set<T> set) {
return set.stream().collect(Collectors.toMap(Function.identity(), Function.identity()));
}
However, I know that usually, a set has a backing field of map. This is the method I use to create the map:
public static <E> Set<E> newConcurrentHashSet() {
return Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
}
Since I only need the keys I thought maybe I can get a view of this field somehow. any idea?
I ended up with a fairly straight-forward one line solution with Java 8 as follows:
Map<String, Foo> map = fooSet.stream().collect(Collectors.toMap(Foo::getKey, e -> e));
fooSet is a set of objects of type Foo, i.e. Set<Foo> fooSet
Foo has a getter called getKey which returns a String
You can convert a Set to a Map (same key and value taken from elements of Set) as shown below:
private <T> Map<T, T> toMap(Set<T> set) {
Map<T, T> map = new ConcurrentHashMap<>();
set.forEach(t -> map.put(t, t));//contains same key and value pair
return map;
}
Improvement of developer's answer:
Map<String, Foo> map = fooSet.stream().collect(Collectors.toMap(Foo::getKey, Function.identity()));
or if you statically import Collectors.toMap and Function.identity:
Map<String, Foo> map = fooSet.stream().collect(toMap(Foo::getKey, identity()));
From comment:
I would like to know which items only on left, which only on right, which in common (similar to map difference)
Use removeAll() and [retainAll()][3].
Example:
Set<Integer> set1 = new HashSet<>(Arrays.asList(1,3,5,7,9));
Set<Integer> set2 = new HashSet<>(Arrays.asList(3,4,5,6,7));
Set<Integer> onlyIn1 = new HashSet<>(set1);
onlyIn1.removeAll(set2);
Set<Integer> onlyIn2 = new HashSet<>(set2);
onlyIn2.removeAll(set1);
Set<Integer> inBoth = new HashSet<>(set1);
inBoth.retainAll(set2);
System.out.println("set1: " + set1);
System.out.println("set2: " + set2);
System.out.println("onlyIn1: " + onlyIn1);
System.out.println("onlyIn2: " + onlyIn2);
System.out.println("inBoth : " + inBoth);
Output
set1: [1, 3, 5, 7, 9]
set2: [3, 4, 5, 6, 7]
onlyIn1: [1, 9]
onlyIn2: [4, 6]
inBoth : [3, 5, 7]
Now, if you want to know all values and where they were found, you can do this (Java 8):
Set<Integer> setA = new HashSet<>(Arrays.asList(1,3,5,7,9));
Set<Integer> setB = new HashSet<>(Arrays.asList(3,4,5,6,7));
Map<Integer, String> map = new HashMap<>();
for (Integer i : setA)
map.put(i, "In A");
for (Integer i : setB)
map.compute(i, (k, v) -> (v == null ? "In B" : "In Both"));
System.out.println("setA: " + setA);
System.out.println("setB: " + setB);
map.entrySet().stream().forEach(System.out::println);
Output
setA: [1, 3, 5, 7, 9]
setB: [3, 4, 5, 6, 7]
1=In A
3=In Both
4=In B
5=In Both
6=In B
7=In Both
9=In A
See similar answer here.
Assuming that your original set is a set of values (no keys in original data!), you will need to specify keys for the newly created map. Guava's Maps.uniqueIndex might be helpful (see here)
Otherwise, if your original set is a set of keys (no values in original data!) that you want to retain, you will need to specify default or specific values for the newly created map. Guava's Maps.toMap might be helpful here. (See more here)
package com.example;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
Set<Foo> s = new HashSet<>();
s.add(new Foo("cccc"));
s.add(new Foo("aaaa"));
s.add(new Foo("bbb"));
Map<String, Foo> m = s.stream().collect(Collectors.toMap(Foo::getKey, Function.identity()));
System.out.println(m);
}
}
class Foo {
String name;
Foo(String name){this.name = name;}
String getKey() {return name;}
}
Important note from the reference:
The returned Collector is not concurrent. For parallel stream
pipelines, the combiner function operates by merging the keys from one
map into another, which can be an expensive operation. If it is not
required that results are inserted into the Map in encounter order,
using toConcurrentMap(Function, Function) may offer better parallel
performance.

How to use Java 8 streams to find all values preceding a larger value?

Use Case
Through some coding Katas posted at work, I stumbled on this problem that I'm not sure how to solve.
Using Java 8 Streams, given a list of positive integers, produce a
list of integers where the integer preceded a larger value.
[10, 1, 15, 30, 2, 6]
The above input would yield:
[1, 15, 2]
since 1 precedes 15, 15 precedes 30, and 2 precedes 6.
Non-Stream Solution
public List<Integer> findSmallPrecedingValues(final List<Integer> values) {
List<Integer> result = new ArrayList<Integer>();
for (int i = 0; i < values.size(); i++) {
Integer next = (i + 1 < values.size() ? values.get(i + 1) : -1);
Integer current = values.get(i);
if (current < next) {
result.push(current);
}
}
return result;
}
What I've Tried
The problem I have is I can't figure out how to access next in the lambda.
return values.stream().filter(v -> v < next).collect(Collectors.toList());
Question
Is it possible to retrieve the next value in a stream?
Should I be using map and mapping to a Pair in order to access next?
Using IntStream.range:
static List<Integer> findSmallPrecedingValues(List<Integer> values) {
return IntStream.range(0, values.size() - 1)
.filter(i -> values.get(i) < values.get(i + 1))
.mapToObj(values::get)
.collect(Collectors.toList());
}
It's certainly nicer than an imperative solution with a large loop, but still a bit meh as far as the goal of "using a stream" in an idiomatic way.
Is it possible to retrieve the next value in a stream?
Nope, not really. The best cite I know of for that is in the java.util.stream package description:
The elements of a stream are only visited once during the life of a stream. Like an Iterator, a new stream must be generated to revisit the same elements of the source.
(Retrieving elements besides the current element being operated on would imply they could be visited more than once.)
We could also technically do it in a couple other ways:
Statefully (very meh).
Using a stream's iterator is technically still using the stream.
That's not a pure Java8, but recently I've published a small library called StreamEx which has a method exactly for this task:
// Find all numbers where the integer preceded a larger value.
Collection<Integer> numbers = Arrays.asList(10, 1, 15, 30, 2, 6);
List<Integer> res = StreamEx.of(numbers).pairMap((a, b) -> a < b ? a : null)
.nonNull().toList();
assertEquals(Arrays.asList(1, 15, 2), res);
The pairMap operation internally implemented using custom spliterator. As a result you have quite clean code which does not depend on whether the source is List or anything else. Of course it works fine with parallel stream as well.
Committed a testcase for this task.
It's not a one-liner (it's a two-liner), but this works:
List<Integer> result = new ArrayList<>();
values.stream().reduce((a,b) -> {if (a < b) result.add(a); return b;});
Rather than solving it by "looking at the next element", this solves it by "looking at the previous element, which reduce() give you for free. I have bent its intended usage by injecting a code fragment that populates the list based on the comparison of previous and current elements, then returns the current so the next iteration will see it as its previous element.
Some test code:
List<Integer> result = new ArrayList<>();
IntStream.of(10, 1, 15, 30, 2, 6).reduce((a,b) -> {if (a < b) result.add(a); return b;});
System.out.println(result);
Output:
[1, 15, 2]
The accepted answer works fine if either the stream is sequential or parallel but can suffer if the underlying List is not random access, due to multiple calls to get.
If your stream is sequential, you might roll this collector:
public static Collector<Integer, ?, List<Integer>> collectPrecedingValues() {
int[] holder = {Integer.MAX_VALUE};
return Collector.of(ArrayList::new,
(l, elem) -> {
if (holder[0] < elem) l.add(holder[0]);
holder[0] = elem;
},
(l1, l2) -> {
throw new UnsupportedOperationException("Don't run in parallel");
});
}
and a usage:
List<Integer> precedingValues = list.stream().collect(collectPrecedingValues());
Nevertheless you could also implement a collector so thats works for sequential and parallel streams. The only thing is that you need to apply a final transformation, but here you have control over the List implementation so you won't suffer from the get performance.
The idea is to generate first a list of pairs (represented by a int[] array of size 2) which contains the values in the stream sliced by a window of size two with a gap of one. When we need to merge two lists, we check the emptiness and merge the gap of the last element of the first list with the first element of the second list. Then we apply a final transformation to filter only desired values and map them to have the desired output.
It might not be as simple as the accepted answer, but well it can be an alternative solution.
public static Collector<Integer, ?, List<Integer>> collectPrecedingValues() {
return Collectors.collectingAndThen(
Collector.of(() -> new ArrayList<int[]>(),
(l, elem) -> {
if (l.isEmpty()) l.add(new int[]{Integer.MAX_VALUE, elem});
else l.add(new int[]{l.get(l.size() - 1)[1], elem});
},
(l1, l2) -> {
if (l1.isEmpty()) return l2;
if (l2.isEmpty()) return l1;
l2.get(0)[0] = l1.get(l1.size() - 1)[1];
l1.addAll(l2);
return l1;
}), l -> l.stream().filter(arr -> arr[0] < arr[1]).map(arr -> arr[0]).collect(Collectors.toList()));
}
You can then wrap these two collectors in a utility collector method, check if the stream is parallel with isParallel an then decide which collector to return.
If you're willing to use a third party library and don't need parallelism, then jOOλ offers SQL-style window functions as follows
System.out.println(
Seq.of(10, 1, 15, 30, 2, 6)
.window()
.filter(w -> w.lead().isPresent() && w.value() < w.lead().get())
.map(w -> w.value())
.toList()
);
Yielding
[1, 15, 2]
The lead() function accesses the next value in traversal order from the window.
Disclaimer: I work for the company behind jOOλ
You can achieve that by using a bounded queue to store elements which flows through the stream (which is basing on the idea which I described in detail here: Is it possible to get next element in the Stream?
Belows example first defines instance of BoundedQueue class which will store elements going through the stream (if you don't like idea of extending the LinkedList, refer to link mentioned above for alternative and more generic approach). Later you just examine the two subsequent elements - thanks to the helper class:
public class Kata {
public static void main(String[] args) {
List<Integer> input = new ArrayList<Integer>(asList(10, 1, 15, 30, 2, 6));
class BoundedQueue<T> extends LinkedList<T> {
public BoundedQueue<T> save(T curElem) {
if (size() == 2) { // we need to know only two subsequent elements
pollLast(); // remove last to keep only requested number of elements
}
offerFirst(curElem);
return this;
}
public T getPrevious() {
return (size() < 2) ? null : getLast();
}
public T getCurrent() {
return (size() == 0) ? null : getFirst();
}
}
BoundedQueue<Integer> streamHistory = new BoundedQueue<Integer>();
final List<Integer> answer = input.stream()
.map(i -> streamHistory.save(i))
.filter(e -> e.getPrevious() != null)
.filter(e -> e.getCurrent() > e.getPrevious())
.map(e -> e.getPrevious())
.collect(Collectors.toList());
answer.forEach(System.out::println);
}
}

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.
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)));
I would group by value and store the values into a TreeMap in order to have my values sorted, then I would get the max value by getting the last entry as next:
Stream.of(1, 3, 5, 3, 2, 3, 5)
.collect(groupingBy(Function.identity(), TreeMap::new, toList()))
.lastEntry()
.getValue()
.forEach(System.out::println);
Output:
5
5
I implemented more generic collector solution with custom downstream collector. Probably some readers might find it useful:
public static <T, A, D> Collector<T, ?, D> maxAll(Comparator<? super T> comparator,
Collector<? super T, A, D> downstream) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BinaryOperator<A> downstreamCombiner = downstream.combiner();
class Container {
A acc;
T obj;
boolean hasAny;
Container(A acc) {
this.acc = acc;
}
}
Supplier<Container> supplier = () -> new Container(downstreamSupplier.get());
BiConsumer<Container, T> accumulator = (acc, t) -> {
if(!acc.hasAny) {
downstreamAccumulator.accept(acc.acc, t);
acc.obj = t;
acc.hasAny = true;
} else {
int cmp = comparator.compare(t, acc.obj);
if (cmp > 0) {
acc.acc = downstreamSupplier.get();
acc.obj = t;
}
if (cmp >= 0)
downstreamAccumulator.accept(acc.acc, t);
}
};
BinaryOperator<Container> combiner = (acc1, acc2) -> {
if (!acc2.hasAny) {
return acc1;
}
if (!acc1.hasAny) {
return acc2;
}
int cmp = comparator.compare(acc1.obj, acc2.obj);
if (cmp > 0) {
return acc1;
}
if (cmp < 0) {
return acc2;
}
acc1.acc = downstreamCombiner.apply(acc1.acc, acc2.acc);
return acc1;
};
Function<Container, D> finisher = acc -> downstream.finisher().apply(acc.acc);
return Collector.of(supplier, accumulator, combiner, finisher);
}
So by default it can be collected to a list using:
public static <T> Collector<T, ?, List<T>> maxAll(Comparator<? super T> comparator) {
return maxAll(comparator, Collectors.toList());
}
But you can use other downstream collectors as well:
public static String joinLongestStrings(Collection<String> input) {
return input.stream().collect(
maxAll(Comparator.comparingInt(String::length), Collectors.joining(","))));
}
If I understood well, you want the frequency of the max value in the Stream.
One way to achieve that would be to store the results in a TreeMap<Integer, List<Integer> when you collect elements from the Stream. Then you grab the last key (or first depending on the comparator you give) to get the value which will contains the list of max values.
List<Integer> maxValues = st.collect(toMap(i -> i,
Arrays::asList,
(l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(toList()),
TreeMap::new))
.lastEntry()
.getValue();
Collecting it from the Stream(4, 5, -2, 5, 5) will give you a List [5, 5, 5].
Another approach in the same spirit would be to use a group by operation combined with the counting() collector:
Entry<Integer, Long> maxValues = st.collect(groupingBy(i -> i,
TreeMap::new,
counting())).lastEntry(); //5=3 -> 5 appears 3 times
Basically you firstly get a Map<Integer, List<Integer>>. Then the downstream counting() collector will return the number of elements in each list mapped by its key resulting in a Map. From there you grab the max entry.
The first approaches require to store all the elements from the stream. The second one is better (see Holger's comment) as the intermediate List is not built. In both approached, the result is computed in a single pass.
If you get the source from a collection, you may want to use Collections.max one time to find the maximum value followed by Collections.frequency to find how many times this value appears.
It requires two passes but uses less memory as you don't have to build the data-structure.
The stream equivalent would be coll.stream().max(...).get(...) followed by coll.stream().filter(...).count().
I'm not really sure whether you are trying to
(a) find the number of occurrences of the maximum item, or
(b) Find all the maximum values in the case of a Comparator that is not consistent with equals.
An example of (a) would be [1, 5, 4, 5, 1, 1] -> [5, 5].
An example of (b) would be:
Stream.of("Bar", "FOO", "foo", "BAR", "Foo")
.max((s, t) -> s.toLowerCase().compareTo(t.toLowerCase()));
which you want to give [Foo, foo, Foo], rather than just FOO or Optional[FOO].
In both cases, there are clever ways to do it in just one pass. But these approaches are of dubious value because you would need to keep track of unnecessary information along the way. For example, if you start with [2, 0, 2, 2, 1, 6, 2], it would only be when you reach 6 that you would realise it was not necessary to track all the 2s.
I think the best approach is the obvious one; use max, and then iterate the items again putting all the ties into a collection of your choice. This will work for both (a) and (b).
If you'd rather rely on a library than the other answers here, StreamEx has a collector to do this.
Stream.of(1, 3, 5, 3, 2, 3, 5)
.collect(MoreCollectors.maxAll())
.forEach(System.out::println);
There's a version which takes a Comparator too for streams of items which don't have a natural ordering (i.e. don't implement Comparable).
System.out.println(
Stream.of(1, 3, 5, 3, 2, 3, 5)
.map(a->new Integer[]{a})
.reduce((a,b)->
a[0]==b[0]?
Stream.concat(Stream.of(a),Stream.of(b)).toArray() :
a[0]>b[0]? a:b
).get()
)
I was searching for a good answer on this question, but a tad more complex and couldn't find anything until I figured it out myself, which is why I'm posting if this helps anybody.
I have a list of Kittens.
Kitten is an object which has a name, age and gender. I had to return a list of all the youngest kittens.
For example:
So kitten list would contain kitten objects (k1, k2, k3, k4) and their ages would be (1, 2, 3, 1) accordingly. We want to return [k1, k4], because they are both the youngest. If only one youngest exists, the function should return [k1(youngest)].
Find the min value of the list (if it exists):
Optional<Kitten> minKitten = kittens.stream().min(Comparator.comparingInt(Kitten::getAge));
filter the list by the min value
return minKitten.map(value -> kittens.stream().filter(kitten -> kitten.getAge() == value.getAge())
.collect(Collectors.toList())).orElse(Collections.emptyList());
The following two lines will do it without implementing a separate comparator:
List<Integer> list = List.of(1, 3, 5, 3, 2, 3, 5);
list.stream().filter(i -> i == (list.stream().max(Comparator.comparingInt(i2 -> i2))).get()).forEach(System.out::println);

Categories