Converting Java while loop to Stream for performance

Viewed 243

I have the following code, which has performance issues due to having to convert a long Stream to an Array. The Board.getReachables() method returns a stream, so it would be much more performant to be able to do this while loop using streams. I am however, unsure as to how to proceed.

 int counter = 1;
 while (check(board, counter)) {
   for (int i = 0; i < board.length; i++) {
     if (board[i] == counter) {
       for (Pos reach2 :
            Board.getReachables(currentTurn.getBoard().pos(i),
                                currentTurn.getBoard())
                 .toArray(Pos[]::new)) {
         if (board[currentTurn.getBoard().index(reach2)] == 0) {
           board[currentTurn.getBoard().index(reach2)] = counter + 1;
         }
       }
     }
   }
   counter++;
 }
3 Answers

Converting the loop to a stream:

AtomicInteger counter = new AtomicInteger(1);
while (check(board, counter.get())) {
   for (int i = 0; i < board.length; i++) {
     if (board[i] == counter.get()) {
         Board.getReachables(currentTurn.getBoard().pos(i), currentTurn.getBoard())
         .filter(reach2 ->  board[currentTurn.getBoard().index(reach2)] == 0)
         .forEach(reach2 -> board[currentTurn.getBoard().index(reach2)] = counter.get() + 1);
     }
   }
   counter.incrementAndGet();
 }

counter converted to AtomicInteger because it is referred to within a lambda and must therefore be effectively final.

Other refactorings left to the reader.

Not tested and just an idea:

    while (check(board, counter)) {
        final int finalCounter = counter; //counter has to be final,
        //otherwise you get a "Local variable counter defined in an enclosing scope must be final or effectively final" error

        Arrays.stream(board) //IntStream
        .filter(b -> b == finalCounter) //  if (board[i] == counter) {
        .mapToObj(i ->Board.getReachables(currentTurn.getBoard().pos(i), currentTurn.getBoard())) //returns Stream<Stream<Pos>>
        .flatMap(Function.identity()) // flatmap maps Stream<Stream<Pos>> to a single (combined) Stream<Pos>
        .filter(pos -> board[currentTurn.getBoard().index(pos)] == 0)  //board[currentTurn.getBoard().index(reach2)] == 0
        .forEach(fp -> board[currentTurn.getBoard().index(fp)] = finalCounter +1); //board[currentTurn.getBoard().index(reach2)] = counter + 1;
        counter++;
    }

I don't think that someone should do it this way, but it was an interesting exercise.

Two things will increase performance:

  1. Don't create the array and loop over it, instead just call .forEach on the Stream.
  2. Only calculate the index once. Unfortunately this means using if instead of .filter()
        int counter = 1;
        while (check(board, counter)) {
            for (int i = 0; i < board.length; i++) {
                if (board[i] == counter) {
                    Board currentTurnBoard = currentTurn.getBoard();
                    int   nextCounter      = counter + 1;
                    Board.getReachables(currentTurnBoard.pos(i), currentTurnBoard)
                         .forEach(reach2 -> {
                             int index = currentTurnBoard.index(reach2);
                             if (board[index] == 0) {
                                 board[index] = nextCounter;
                             }
                             return;
                         });
                }
            }
            counter++;
        }
Related