16

I have a Stream of Integer and I would like to find the two numbers whose sum is equals to another number. So I came up with the following solution:

BiPredicate<Integer, Integer> p = (price1, price2) -> price1.intValue() + price2.intValue() == moneyQty;
flavoursPrices.filter(p);

But the filter method does not receive a BiPredicate. Why not? What is an alternative for that?

user2919910
  • 575
  • 1
  • 7
  • 17
  • 3
    A stream works one element at a time. A filter checks a condition on each element, and decides whether to pass it or not. You can't use a two-parameter predicate when you have only one object to decide on. – RealSkeptic Mar 15 '16 at 19:54
  • 1
    Because a Stream is a sequence of Integer instances, not a sequence of pairs of integers. – JB Nizet Mar 15 '16 at 19:54

4 Answers4

24

You can still work with Bipredicate. The argument that the filter-method needs is a Predicate, so here is an example of how to use this BiPredicate:

 BiPredicate<Integer, Integer> p = (price1, price2) -> price1.intValue() + price2.intValue() == moneyQty;

flavoursPrices.stream().filter(el->p.test(el.price1,el.price2));

In this example flavoursPrices must be a List.

The lambda that we are using:

el->p.test(el.price1,el.price2)

Is replacing the anonymous inner class declaration for creating a new Predicate out of the BiPredicate:

 Predicate<Integer> arg =new Predicate<Integer>() {
        @Override
        public boolean test(Element el) {
            return p.test(el.price1,el.price2);
        }
    };

So to filter the stream, we are creating a new Predicate for every element coming from the stream and than we use this Predicate as argument to use it's test-method. The big advantage of this is, that we don't have to create enormous amounts of Predicates in advance, but we can pass every element in the lambda function and get it's attributes.

Matt
  • 375
  • 3
  • 10
4

Because filter does not work that way, it only filters one collection at a time. For your task, you need something like Python's itertools.product; this can be implemented using flatMap. In Scala, this looks as short as:

prices.flatMap(p1 => 
  prices.flatMap(p2 => 
    if (p1 + p2 == 5) List((p1, p2)) else List()))

If you want to do it in (native) Java, something like this comes out:

import java.util.*;
import java.util.stream.*;

public class Main {

    public static void main(String[] args) {

        List<Integer> prices =  Arrays.asList(1,2,3,4,5,6,7,8,9);

        List<List<Integer>> result = prices.stream().flatMap(p1 ->
            prices.stream().flatMap(p2 ->
                p1 + p2 == 5 ? Stream.of(Arrays.asList(p1, p2)) : Stream.empty()
            )
        ).collect(Collectors.toList());

        System.out.println(result);

    }
}

... which prints [[1, 4], [2, 3], [3, 2], [4, 1]]. But I wouldn't use that in reality ;) At least, a tuple class would be nice to have.

It is worth noting that flatMap one of the most powerful functions there is; almost all stream transformations (filter, map, cartesian product, flatten) can be implemented using it. And rest (like zip) is almost always a fold/reduce (or, rarely, an unfold).

phipsgabler
  • 20,535
  • 4
  • 40
  • 60
  • Yeah. I was curious but it looks way way easier in Scala. Thanks for the exampe :) – Yassin Hajaj Mar 15 '16 at 21:22
  • @phg, for sake of readability, you can simplify your bigger block by `p1 + p2 == 5 ? Stream.of(Arrays.asList(p1, p2)) : Stream.empty()`, resulting in a `List>`. Great answer! – ericbn Mar 27 '16 at 17:01
  • @YassinHajaj It gets better; the same can be written as `for { p1 <- prices; p2 <- prices; if p1 + p2 == 5 } yield (p1, p2)`. – Alexey Romanov Sep 01 '16 at 08:40
2

Predicate != BiPredicate

Stream#filter takes a Predicate (Object -> boolean) will process each element at a time.

You might want to check the StreamEx library for a solution.

It has methods to pair consecutive elements in streams.

StreamEx

Community
  • 1
  • 1
Yassin Hajaj
  • 21,337
  • 9
  • 51
  • 89
  • 2
    @user2919910 As an alternative, you could use `flatMap`: `prices.flatMap(p1 => prices.flatMap(p2 => if (p1 + p2 == 5) List((p1, p2)) else List()))` (in Scala, but should be easy to translate). – phipsgabler Mar 15 '16 at 20:12
  • @phg That would be interesting, would you mind posting an answer with your solution? – Yassin Hajaj Mar 15 '16 at 20:15
  • I'll try, but give me some time to dig out my Java skills ;) – phipsgabler Mar 15 '16 at 20:23
  • 1
    So, [there](http://stackoverflow.com/a/36022314/1346276) you have that abomination... I always wonder why they didn't include a complete set of functionality in the Stream classes. Or provide tuples. – phipsgabler Mar 15 '16 at 21:20
1

Where's how you can implement your solution with the StreamEx library:

StreamEx.ofPairs(prices, (p1, p2) -> p1 + p2 == 5 ? StreamEx.of(p1, p2) : StreamEx.empty())
            .flatMap(Function.identity())
            .forEach(price -> System.out.println(price));

You may also want to create your own class to encapsulate the pairs.

ericbn
  • 10,163
  • 3
  • 47
  • 55