Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

converting for loop to java 8 stream

I was playing around with Java 8. I had some trouble converting this for loop into Java 8 Stream.

for (int y = 0; y < 5; y ++) {
    for (int x = y; x < 10; x += 2) {
        System.out.println(x+y);
    }
}

Please help!

like image 700
user3575416 Avatar asked May 15 '14 21:05

user3575416


People also ask

Is Java 8 stream faster than for loop?

The short version basically is, if you have a small list; for loops perform better, if you have a huge list; a parallel stream will perform better. And since parallel streams have quite a bit of overhead, it is not advised to use these unless you are sure it is worth the overhead.

Is Java stream better than for loop?

Remember that loops use an imperative style and Streams a declarative style, so Streams are likely to be much easier to maintain. If you have a small list, loops perform better. If you have a huge list, a parallel stream will perform better.

Is stream API faster than for loop Java?

Yes, streams are sometimes slower than loops, but they can also be equally fast; it depends on the circumstances. The point to take home is that sequential streams are no faster than loops.

Does Java 8 support streams?

Java 8 offers the possibility to create streams out of three primitive types: int, long and double. As Stream<T> is a generic interface, and there is no way to use primitives as a type parameter with generics, three new special interfaces were created: IntStream, LongStream, DoubleStream.


2 Answers

The canonical way of converting nested loops is to use flatMap on a stream, e.g.

IntStream.range(0, 5).flatMap(i->IntStream.range(i, 10))
  .forEach(System.out::println);

The tricky part on your task is the increment by two as this has no direct equivalent in the stream API. There are two possibilities:

  1. Use IntStream.iterate(y, x->x+2) to define start value and increment. Then you have to modify the infinite stream by limiting the number of elements: .limit((11-y)/2).

    So the resulting code for your loop would look like:

    IntStream.range(0, 5)
        .flatMap(y->IntStream.iterate(y, x->x+2).limit((11-y)/2)
        .map(x -> x+y)).forEach(System.out::println);
    
  2. Use IntStream.range(0, (11-y)/2) to create an stream of the desired number of ascending ints and modify it with .map(t->y+t*2) to have it produce the desired values of your inner for loop.

    Then, the resulting code for your loop would look like:

    IntStream.range(0, 5)
        .flatMap(y->IntStream.range(0, (11-y)/2).map(t->y+t*2).map(x -> x+y))
        .forEach(System.out::println);
    
like image 156
Holger Avatar answered Nov 04 '22 07:11

Holger


As Holger pointed out here already, it is certainly doable. The question though is: Why?
I am suspecting an XY problem, streams are not the solution to everything, that is an important fact to remember.

Take a look at what your code is doing, and create a method that does what you want to do, with as much variability as possible, you are:

  • Having an y maximum (considering you always start at 0)
  • Having an x maximum (considering you always start at 0)
  • Having an y increment operation. (Assuming the +1 is not fixed)
  • Having an x increment operation.
  • Then an operation with the x and y.
  • Then you consume the result of the operation.

Hence I propose the following, variability may be reduced if this is not exactly what you want, in other words: You can hardcore more if neccessary.

private void doMyDoubleLoop(
        final int yEnd, final IntUnaryOperator yIncrementOperator, 
        final int xEnd, final IntUnaryOperator xIncrementOperator,
        final IntBinaryOperator combiner, final IntConsumer consumer
) {
    for (int y = 0; y < yEnd; y = yIncrementOperator.applyAsInt(y)) {
        for (int x = y; x < xEnd; x = xIncrementOperator.applyAsInt(x)) {
            consumer.accept(combiner.applyAsInt(x, y));
        }
    }
}

Used as:

doMyDoubleLoop(5, y -> y + 1, 10, x -> x + 2, (x, y) -> x + y, System.out::println);

As I said, this might be overkill, so assuming everything revolving around the for-loops is fixed, it suddenly is a lot nicer:

private void doMyInternalDoubleLoop(final IntBinaryOperator combiner, final IntConsumer consumer) {
    for (int y = 0; y < 5; y++) {
        for (int x = y; x < 10; x += 2) {
            consumer.accept(combiner.applyAsInt(x, y));
        }
    }
}

Used as:

doMyInternalDoubleLoop((x, y) -> x + y, System.out::println);

This is a pattern that I would suggest to use if you have a class on which you have operations that use this double-loop a lot, but do not want to copy around the loop, as it should be with DRY (Don't Repeat Yourself) principle.

like image 35
skiwi Avatar answered Nov 04 '22 06:11

skiwi