Java 8 forEach through multiple IntStreams

I have the following code:

IntStream.range(0, width).forEach(x1 -> { IntStream.range(0, height).forEach(y1 -> { IntStream.rangeClosed(x1-1, x1+1).forEach(x2 -> { IntStream.rangeClosed(y1-1, y1+1).forEach(y2 -> { if ((x1 != x2 || y1 != y2) && getNode(x2, y2) != null){ getNode(x1, y1).registerObserverAtNeighbor(getNode(x2, y2)); } }); }); }); }); 

Is there a way to write above using fewer nested statements? This is basically "for each node from (0,0) to (width, height) the register observer at nodes from (x-1, y-1) to (x + 1, y + 1), but not at home."

+5
source share
3 answers

You have basically 4 nested loops. This makes sense because you iterate over two dimensions of the matrix, and then for each node you iterate over a small matrix consisting of its neighbors.

Something like that.

 0000000 0---000 0-X-000 0---000 0000000 

I think you could use the recursive function for syntax only, although nothing really worked out.

 iterateLambdas(0, width, 0, height, 1); public static void iterateLambdas( int start1, int end1, int start2, int end2, int depth) { IntStream.range(start1, end1).forEach(x1 -> { IntStream.range(start2, end2).forEach(y1 -> { if (depth != 0) { iterateLambdas(x1 - 1, x1 + 2, y1 - 1, y1 + 2, depth - 1); } else { // Current node : (start1 + 1), (start2 + 1) // Current neighbour : x1, y1); // Your logic here } }); }); } 
+1
source

Basically, you can replace nested loops with Stream using flatMap . This requires that you select an item type that can store information equivalent to loop variables if you need it. In your case, these are two values ​​for x and y . This would simplify the code if your Node class stores this information, since you can easily iterate over nodes, not int values. Since you did not specify the capabilities of your Node class, here is an example that uses a long[] size two to store points:

 IntStream.range(0, width).boxed() .flatMap(x->IntStream.range(0, height).mapToObj(y->new int[]{ x, y })) .forEach(p1 -> { Consumer<Node> register=getNode(p1[0], p1[1])::registerObserverAtNeighbor; IntStream.rangeClosed(p1[0]-1, p1[0]+1).boxed() .flatMap(x->IntStream.rangeClosed(p1[1]-1, p1[1]+1).mapToObj(y->new int[]{ x,y })) .filter(p2 -> (p1[0] != p2[0] || p1[1] != p2[1])) .map(point -> getNode(point[0], point[1])) .filter(node -> node != null) .forEach(register); }); 

It still simplifies the innermost code by moving the code outside where possible, for example. getNode calls. You can also simplify the code by setting the repetitive task of creating Stream over points for an area in a method:

 static Stream<int[]> area(int x0, int x1, int y0, int y1) { return IntStream.range(x0, x1).boxed() .flatMap(x->IntStream.range(y0, y1).mapToObj(y->new int[]{ x, y })); } 

Then you can use it as follows:

 area(0, width, 0, height).forEach(p1 -> { Consumer<Node> register=getNode(p1[0], p1[1])::registerObserverAtNeighbor; area(p1[0]-1, p1[0]+2, p1[1]-1, p1[1]+2) .filter(p2 -> (p1[0] != p2[0] || p1[1] != p2[1])) .map(point -> getNode(point[0], point[1])) .filter(node -> node != null) .forEach(register); }); 

It may still be easier if you use / use a dedicated point class or the node class contains point information (and at best has a comparison method for it).

+1
source

Since you are working with nodes, I suggest creating node flows first. Note that I make some assumptions about nodes.

 getNodes(0, width - 1, 0, height - 1).forEach(node -> { getNodes(node.getX() - 1, node.getX() + 1, node.getY() - 1, node.getY() + 1) .filter(neighbor -> !neighbor.equals(node)) .forEach(neighbor -> node.registerObserverAtNeighbor(neighbor)); }); 

Creating a stream using your approach:

 private static Stream<Node> getNodes(int x1, int x2, int y1, int y2) { return IntStream.rangeClosed(x1, x2) .mapToObj(x -> (Stream<Node>)IntStream.rangeClosed(y1, y2).mapToObj(y -> getNode(x, y))) .flatMap(nodes -> nodes) .filter(node -> node != null); } 
0
source

Source: https://habr.com/ru/post/1204976/


All Articles