24

I have the following code and would like to implement it using lambda functions just for fun. Can it be done using the basic aggregate operations?

List<Integer> result = new ArrayList<>();

for (int i = 1; i <= 10; i++) {
    if (10 % i == 0) {
        result.add(i);
        if (i != 5) {
            result.add(10 / i);
        }
    }
}

Using lambda:

List<Integer> result = IntStream.rangeClosed(1, 10)
                                .boxed()
                                .filter(i -> 10 % i == 0)
                                // a map or forEach function here?
                                // .map(return 10 / i -> if i != 5)
                                .collect(Collectors.toList());

5 Answers 5

53

The essential observation here is that your problem involves a non-isomorphic transformation: a single input element may map to zero, one, or two output elements. Whenever you notice this, you should immediately start looking for a solution which involves flatMap instead of map because that's the only way to achieve such a general transformation. In your particular case you can first apply filter for a one-to-zero element mapping, then flatMap for one-to-two mapping:

List<Integer> result =
    IntStream.rangeClosed(1, 10)
             .filter(i -> 10 % i == 0)
             .flatMap(i -> i == 5 ? IntStream.of(i) : IntStream.of(i, 10 / i))
             .boxed()
             .collect(toList());

(assuming import static java.util.stream.Collectors.toList)

Sign up to request clarification or add additional context in comments.

1 Comment

I like how you not only answer the question but you also teach how to think about the problem to get to the solution.
6

You could declare a body for a lambda. For example:

Runnable run = () -> System.out.println("Hey");

Could be

Runnable run = () -> {
    System.out.println("Hey");
};

Within that body, you can create nested statements:

Runnable run = () -> {
    int num = 5;

    if(num == 5) {
        System.out.println("Hey");
    }
};

3 Comments

but is it possible to do it with map, filter, etc? I am trying to learn the basics of lambda functions. Thanks.
@Z-1 I'm not sure if filters are able to do this, but the syntax would be .filter( i -> { return yourLogic; } )
@Z-1 A lambda expression arises from the use of a functional interface (an interface with only 1 abstract method, such as Runnable; may have multiple default methods). For example, a Thread accepts a Runnable in it's constructor. We could write new Thread(() -> { });. You can even create your own functional interfaces. So to answer "possible to do it with map and filter": yes. It works for all lambdas.
3

Use flatMap as you are trying to add elements into the pipeline or a 1-to-many mapping. Map is a one to one mapping.

ArrayList<Integer> result = (ArrayList<Integer>) IntStream.rangeClosed(1, 10)
                .boxed()
                .filter(i -> 10 % i == 0)
                .flatMap((Integer i) -> {return i!=5 ? Stream.of(i, (10/i)):Stream.of(i);})
                .collect(Collectors.toList());

This results in the same list as

ArrayList<Integer> result2 = new ArrayList<Integer>();

        for (int i = 1; i <= 10; i++) {
            if (10 % i == 0) {
                result2.add(i);
                if (i != 5) {
                    result2.add(10 / i);
                }
            }
        }

In case your wondering which way is faster the loop method is ~3 times faster than using streams.

Benchmark                     Mode  Cnt      Score     Error  Units
testStreams.Bench.loops       avgt    5     75.221 ±   0.576  ns/op
testStreams.Bench.streams     avgt    5    257.713 ±  13.125  ns/op

3 Comments

This is interesting. Thanks for the benchmark. I noticed that too, Streams is much slower than traditional for loop.
Depends on the application really, operations on ints are some of the simplest operartion you can do, for what you were doing here the overhead in setting up the stream is too high. I found the second answer on this post helpful.
the stream overhead does indeed get smaller as the loop range grows larger. doesn't quite vanish in this case though.
1

You can do this:

List<Integer> result1 = IntStream
    .rangeClosed(1, 10)
    .boxed()
    .filter(i -> 10 % i == 0)
    .map(i -> (i != 5 ? Stream.of(i, 10 / i) : Stream.of(i)))
    .flatMap(Function.identity())
    .collect(Collectors.toList());

Comments

0

Try using flatMap:

List<Integer> result = IntStream.rangeClosed(1, 10)
        .boxed()
        .flatMap((i) -> {
            List<Integer> results = new ArrayList<>();
            if (10 % i == 0) {
                results.add(i);
                if (i != 5) {
                    results.add(10 / i);
                }
            }
            return results.stream();
        })
        .collect(Collectors.toList());

See http://ideone.com/EOBiEP

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.