converting for loop to java 8 stream

旧城冷巷雨未停 提交于 2020-01-03 13:03:11

问题


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!


回答1:


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);
    



回答2:


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.




回答3:


The way I would do this is to use a filter on the inner IntStream:

IntStream
.range(0, 5)
.flatMap(
        y ->
        IntStream
        .range(y, 10)
        .filter(x -> (x-y) % 2 == 0)
        .map(x -> x+y))
.forEach(System.out::println);

I've been using this pattern for segmenting data: E.g.

IntStream
.range(first, last)
.filter(index -> (index-first) % segment == 0)
.forEach(
        System.out.printf(
                "Doing segment %s to %s%n", 
                index, 
                Math.min(index+seg-1, last)));



回答4:


I'm still trying to figure this out myself. The following gets no points for style:

IntStream.range(0, 5).forEach(y -> 
//<kludge> 
//I would appreciate it if someone would replace this with something smarter.
    IntStream.iterate(y, x -> x + 2)
        .limit(100)
        .filter(x -> x < 10)
//</kludge>
        .forEach( x -> System.out.println(y+x)));

The x += 2 is the tricky part, which would have been trivial if the range method had an "increment" parameter. I used iterate to do the increments myself, but iterate produces an infinite stream. I used filter to restrict it to the range I want, and put in an arbitrary limit so that it wouldn't just overflow and start spitting out large negative numbers that are < 10.



来源:https://stackoverflow.com/questions/23688967/converting-for-loop-to-java-8-stream

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!