Java predicate - match against first predicate [duplicate] - java

I've just started playing with Java 8 lambdas and I'm trying to implement some of the things that I'm used to in functional languages.
For example, most functional languages have some kind of find function that operates on sequences, or lists that returns the first element, for which the predicate is true. The only way I can see to achieve this in Java 8 is:
lst.stream()
.filter(x -> x > 5)
.findFirst()
However this seems inefficient to me, as the filter will scan the whole list, at least to my understanding (which could be wrong). Is there a better way?

No, filter does not scan the whole stream. It's an intermediate operation, which returns a lazy stream (actually all intermediate operations return a lazy stream). To convince you, you can simply do the following test:
List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
int a = list.stream()
.peek(num -> System.out.println("will filter " + num))
.filter(x -> x > 5)
.findFirst()
.get();
System.out.println(a);
Which outputs:
will filter 1
will filter 10
10
You see that only the two first elements of the stream are actually processed.
So you can go with your approach which is perfectly fine.

However this seems inefficient to me, as the filter will scan the whole list
No it won't - it will "break" as soon as the first element satisfying the predicate is found. You can read more about laziness in the stream package javadoc, in particular (emphasis mine):
Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, "find the first String with three consecutive vowels" need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.

return dataSource.getParkingLots()
.stream()
.filter(parkingLot -> Objects.equals(parkingLot.getId(), id))
.findFirst()
.orElse(null);
I had to filter out only one object from a list of objects. So i used this, hope it helps.

In addition to Alexis C's answer, If you are working with an array list, in which you are not sure whether the element you are searching for exists, use this.
Integer a = list.stream()
.peek(num -> System.out.println("will filter " + num))
.filter(x -> x > 5)
.findFirst()
.orElse(null);
Then you could simply check whether a is null.

Already answered by #AjaxLeung, but in comments and hard to find.
For check only
lst.stream()
.filter(x -> x > 5)
.findFirst()
.isPresent()
is simplified to
lst.stream()
.anyMatch(x -> x > 5)

import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
// Stream is ~30 times slower for same operation...
public class StreamPerfTest {
int iterations = 100;
List<Integer> list = Arrays.asList(1, 10, 3, 7, 5);
// 55 ms
#Test
public void stream() {
for (int i = 0; i < iterations; i++) {
Optional<Integer> result = list.stream()
.filter(x -> x > 5)
.findFirst();
System.out.println(result.orElse(null));
}
}
// 2 ms
#Test
public void loop() {
for (int i = 0; i < iterations; i++) {
Integer result = null;
for (Integer walk : list) {
if (walk > 5) {
result = walk;
break;
}
}
System.out.println(result);
}
}
}

A generic utility function with looping seems a lot cleaner to me:
static public <T> T find(List<T> elements, Predicate<T> p) {
for (T item : elements) if (p.test(item)) return item;
return null;
}
static public <T> T find(T[] elements, Predicate<T> p) {
for (T item : elements) if (p.test(item)) return item;
return null;
}
In use:
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
Integer[] intArr = new Integer[]{1, 2, 3, 4, 5};
System.out.println(find(intList, i -> i % 2 == 0)); // 2
System.out.println(find(intArr, i -> i % 2 != 0)); // 1
System.out.println(find(intList, i -> i > 5)); // null

Improved One-Liner answer: If you are looking for a boolean return value, we can do it better by adding isPresent:
return dataSource.getParkingLots().stream().filter(parkingLot -> Objects.equals(parkingLot.getId(), id)).findFirst().isPresent();

Related

find largest item in list that exceeds a constant value

Given a list of prices, I want to find the index of the the largest price that exceeds a certain minimum. My current solution looks like this:
public class Price {
public static Integer maxPriceIndex(List<Integer> prices, Integer minPrice) {
OptionalInt maxPriceIndexResult = IntStream.range(0, prices.size())
.reduce((a, b) -> prices.get(a) > prices.get(b) ? a : b);
if (maxPriceIndexResult.isPresent()) {
int maxPriceIndex = maxPriceIndexResult.getAsInt();
int maxFuturePrice = prices.get(maxPriceIndex);
if (maxFuturePrice > minPrice) {
return maxPriceIndex;
}
}
return null;
}
public static void main(String[] args) {
List<Integer> prices = Arrays.asList(5, 3, 2);
Integer result = maxPriceIndex(prices, 6);
System.out.println("Final result: " + result);
}
}
I don't like this mix of imperative and functional code, but can't figure out a way of changing the reducer so that it also compares the price with minPrice. Is there a purely functional solution to this problem?
You can do the filter before finding the max.
IntStream.range(0, prices.size())
.filter(i -> prices.get(i) > minPrice)
.reduce((a, b) -> prices.get(a) > prices.get(b) ? a : b);
Apart from filtering the stream as you process, you can perform a max based on the custom comparator instead of reduce as:
return IntStream.range(0, prices.size())
.filter(i -> prices.get(i) > minPrice)
.boxed()
.max(Comparator.comparingInt(prices::get))
.orElse(null);
Judging from all the answers you got, it's easy to make poorly performing implementations by accident. Not one of the other answers is as fast as the code you originally wrote. (#MikeFHay's is pretty good, though)
Maybe just do:
int index = IntStream.range(0, prices.size())
.reduce((a, b) -> prices.get(a) > prices.get(b) ? a : b)
.orElse(-1);
return (index >= 0 && prices.get(index) > minPrice) ? index : null;
Optionals and Streams are handy to have around, but their use is not mandatory, and you don't have to jump through hoops to use them.
What you really want here is an OptionalInt.filter or OptionalInt.boxed, but Java doesn't provide them.
First filter all values greater than minPrice and then sort them in reverseOrder, next get the index of first max element using findFirst value or if list is empty return null
return list.stream()
.filter(i->i>minPrice)
.sorted(Comparator.reverseOrder())
.findFirst()
.map(v->list.indexOf(v))
.orElse(null);
If you want to get the last index of max element you can use lastIndexOf method
.map(v->list.lastIndexOf(v))

java stream find match or the last one?

How to find the first match or the last element in a list using java stream?
Which means if no element matches the condition,then return the last element.
eg:
OptionalInt i = IntStream.rangeClosed(1,5)
.filter(x-> x == 7)
.findFirst();
System.out.print(i.getAsInt());
What should I do to make it return 5;
Given the list
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
You could just do :
int value = list.stream().filter(x -> x == 2)
.findFirst()
.orElse(list.get(list.size() - 1));
Here if the filter evaluates to true the element is retrieved, else the last element in the last is returned.
If the list is empty you could return a default value, for example -1.
int value = list.stream().filter(x -> x == 2)
.findFirst()
.orElse(list.isEmpty() ? -1 : list.get(list.size() - 1));
You can use reduce() function like that:
OptionalInt i = IntStream.rangeClosed(1, 5)
.reduce((first, second) -> first == 7 ? first : second);
System.out.print(i.getAsInt());
Basically I would use one of the following two methods or deviations thereof:
Stream variant:
<T> T getFirstMatchOrLast(List<T> list, Predicate<T> filter, T defaultValue) {
return list.stream()
.filter(filter)
.findFirst()
.orElse(list.isEmpty() ? defaultValue : list.get(list.size() - 1));
}
non-stream variant:
<T> T getFirstMatchOrLast(Iterable<T> iterable, Predicate<T> filter, T defaultValue) {
T relevant = defaultValue;
for (T entry : iterable) {
relevant = entry;
if (filter.test(entry))
break;
}
return relevant;
}
Or as also Ilmari Karonen suggested in the comment with Iterable<T> you are then able to even call stream::iterator in case you really deal with a Stream instead of a List. Calling the shown methods would look as follows:
getFirstMatchOrLast(Arrays.asList(1, 20, 3), i -> i == 20, 1); // returns 20
getFirstMatchOrLast(Collections.emptyList(), i -> i == 3, 20); // returns 20
getFirstMatchOrLast(Arrays.asList(1, 2, 20), i -> i == 7, 30); // returns 20
// only non-stream variant: having a Stream<Integer> stream = Stream.of(1, 2, 20)
getFirstMatchOrLast(stream::iterator, i -> i == 7, 30); // returns 20
I wouldn't use reduce here because it sounds wrong to me in the sense, that it also goes through the whole entries even though the first entry could have matched already, i.e. it doesn't short-circuit anymore. Moreover for me it isn't as readable as filter.findFirst.orElse... (but that's probably just my opinion)
I probably would then even end up with something as follows:
<T> Optional<T> getFirstMatchOrLast(Iterable<T> iterable, Predicate<T> filter) {
T relevant = null;
for (T entry : iterable) {
relevant = entry;
if (filter.test(entry))
break;
}
return Optional.ofNullable(relevant);
}
// or transform the stream variant to somethinng like that... however I think that isn't as readable anymore...
so that calls would rather look like:
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElseThrow(...)
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElse(0);
getFirstMatchOrLast(Arrays.asList(1, 2, 3, 5), i -> i == 7).orElseGet(() -> /* complex formula */);
getFirstMatchOrLast(stream::iterator, i -> i == 5).ifPresent(...)
if you want to do this in one pipeline then you could do:
int startInc = 1;
int endEx = 5;
OptionalInt first =
IntStream.concat(IntStream.range(startInc, endEx)
.filter(x -> x == 7), endEx > 1 ? IntStream.of(endEx) : IntStream.empty())
.findFirst();
but you're probably better off collecting the generated numbers into a list then operate on it as follows:
// first collect the numbers into a list
List<Integer> result = IntStream.rangeClosed(startInc,endEx)
.boxed()
.collect(toList());
// then operate on it
int value = result.stream()
.filter(x -> x == 7)
.findFirst()
.orElse(result.get(result.size() - 1));
Alternatively, if you want to make the latter return an empty Optional in the case of the source being empty (if that's a possible scenario) instead of an exception then you could do:
List<Integer> result = IntStream.rangeClosed(startInc,endEx)
.boxed()
.collect(toList());
Optional<Integer> first =
Stream.concat(result.stream().filter(x -> x == 7), result.isEmpty() ?
Stream.empty() : Stream.of(result.get(result.size() - 1)))
.findFirst();
I am not sure why you really want to use streams for that, a simple for-loop would be enough:
public static <T> T getFirstMatchingOrLast(List<? extends T> source, Predicate<? super T> predicate){
// handle empty case
if(source.isEmpty()){
return null;
}
for(T t : source){
if(predicate.test(t)){
return t;
}
}
return source.get(source.size() -1);
}
Which then can be called like:
Integer match = getFirstMatchingOrLast(ints, i -> i == 7);

Find missing integer in a sequential sorted stream

Let's say I have a list
ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));
How do I find "N4", I mean, how I find that the missing integer is 4?
What I've tried so far
Integer missingID = arr.stream().map(p -> Integer.parseInt(p.substring(1))).sorted()
.reduce((p1, p2) -> (p2 - p1) > 1 ? p1 + 1 : 0).get();
This doesn't work because reduce is not intended to work in the way I need in this situation, actually, I have no idea how do that.
If there's no missing number, than the next must be "N6" - or just 6 - (in this example)
It must be done with java standard stream's library, no use of third parties.
The algorithm to implement here is based from this one: to find the missing number in a sequence of integers, the trick is to:
calculate the sum of the elements in the sequence.
calculate the sum of the elements the sequence would have with the missing number: this is easy to do since we can determine the minimum, the maximum and we know that the sum from a sequence of integer going from min to max is max*(max+1)/2 - (min-1)*min/2.
find the difference between those two sums: that's our missing number
In this case, we can collect statistics on our Stream by first mapping to an IntStream formed by only the numbers themselves and then calling summaryStatistics(). This returns a IntSummaryStatistics that has all the values we want: min, max and sum:
public static void main(String[] args) {
List<String> arr = Arrays.asList("N3", "N7", "N4", "N5", "N2");
IntSummaryStatistics statistics =
arr.stream()
.mapToInt(s -> Integer.parseInt(s.substring(1)))
.summaryStatistics();
long max = statistics.getMax();
long min = statistics.getMin();
long missing = max*(max+1)/2 - (min-1)*min/2 - statistics.getSum();
System.out.println(missing); // prints "6" here
}
If there is no missing number, this will print 0.
Here's the solution involving the pairMap operation from my free StreamEx library. It prints all the missing elements of the sorted input:
ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));
StreamEx.of(arr).map(n -> Integer.parseInt(n.substring(1)))
.pairMap((a, b) -> IntStream.range(a+1, b))
.flatMapToInt(Function.identity())
.forEach(System.out::println);
The pairMap operation allows you to map every adjacent pair of the stream to something else. Here we map them to the streams of the skipped numbers, then flatten these streams.
The same solution is possible without third-party library, but looks more verbose:
ArrayList<String> arr = new ArrayList(Arrays.asList("N1", "N2", "N3", "N5"));
IntStream.range(0, arr.size()-1)
.flatMap(idx -> IntStream.range(
Integer.parseInt(arr.get(idx).substring(1))+1,
Integer.parseInt(arr.get(idx+1).substring(1))))
.forEach(System.out::println);
If there's only ONE missing number in the array, and if all numbers are positive, you could use the XOR algorithm, as explained in this question and its answers:
List<String> list = Arrays.asList("N5", "N2", "N3", "N6");
int xorArray = list.stream()
.mapToInt(p -> Integer.parseInt(p.substring(1)))
.reduce(0, (p1, p2) -> p1 ^ p2);
int xorAll = IntStream.rangeClosed(2, 6)
.reduce(0, (p1, p2) -> p1 ^ p2);
System.out.println(xorArray ^ xorAll); // 4
The advantage of this approach is that you don't need to use extra data structures, all you need is a couple of ints.
EDIT as per #Holger's comments below:
This solution requires you to know the range of the numbers in advance. Although on the other hand, it doesn't require the list and stream to be sorted.
Even if the list wasn't sorted, you could still get min and max (hence, the range) with IntSummaryStatistics, but this would require an extra iteration.
You could create a state object which is used to transform a single input stream into multiple streams of missing entries. These missing entry streams can then be flat mapped to produce a single output:
public class GapCheck {
private String last;
public GapCheck(String first) {
last = first;
}
public Stream<String> streamMissing(String next) {
final int n = Integer.parseInt(next.replaceAll("N", ""));
final int l = Integer.parseInt(last.replaceAll("N", ""));
last = next;
return IntStream.range(l + 1, n).mapToObj(Integer::toString);
}
}
Usage:
final List<String> arr = new ArrayList(Arrays.asList("N1", "N3", "N5"));
arr.stream()
.flatMap(new GapCheck(arr.get(0))::streamMissing)
.forEach(System.out::println);
output:
2
4
This is more work than you might expect, but it can be done with a collect call.
public class Main {
public static void main(String[] args) {
ArrayList<String> arr = new ArrayList<String>(Arrays.asList("N1", "N2", "N3", "N5", "N7", "N14"));
Stream<Integer> st = arr.stream().map(p -> Integer.parseInt(p.substring(1))).sorted();
Holder<Integer> holder = st.collect(() -> new Holder<Integer>(),
(h, i) -> {
Integer last = h.getProcessed().isEmpty() ? null : h.getProcessed().get(h.getProcessed().size() - 1);
if (last != null) {
while (i - last > 1) {
h.getMissing().add(++last);
}
}
h.getProcessed().add(i);
},
(h, h2) -> {});
holder.getMissing().forEach(System.out::println);
}
private static class Holder<T> {
private ArrayList<T> processed;
private ArrayList<T> missing;
public Holder() {
this.processed = new ArrayList<>();
this.missing = new ArrayList<>();
}
public ArrayList<T> getProcessed() {
return this.processed;
}
public ArrayList<T> getMissing() {
return this.missing;
}
}
}
This prints
4
6
8
9
10
11
12
13
Note that this sort of thing isn't really a particularly strong fit for Streams. All of the stream processing methods will tend to pass you each item exactly one time, so you need to handle all runs of missing numbers at once, and in the end, you're writing kind of a lot of code to avoid just writing a loop.
Here is one solution using pure streams, albeit not very efficient.
public void test() {
List<String> arr = new ArrayList(
Arrays.asList("N1", "N2", "N3", "N5", "N7"));
List<Integer> list = IntStream
.range(1, arr.size())
.mapToObj(t -> new AbstractMap.SimpleEntry<Integer, Integer>(
extract(arr, t), extract(arr, t) - extract(arr, t - 1)))
.filter(t -> t.getValue() > 1)
.map(t -> t.getKey() - 1)
.collect(Collectors.toList());
System.out.println(list);
}
private int extract(List<String> arr, int t) {
return Integer.parseInt(arr.get(t).substring(1));
}
Major performance block will be because of repeated parsing of list elements. However, this solution will be able to provide all missing numbers.

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