4

I'm practicing in Dynamic Programming and I'd like, also, to experiment in Lambda Expressions (to be read "Learn how to code Lambda Expressions").

Let's suppose I'm working on two matrices: MatrixA[][] and MatrixB[][]. I need to check all the cells in MatrixA and incrementally populate them, at the same time populating some entry of the MatrixB.

This is my code:

int[][] A = new int[n][m];
String[][] B = new String[n][m];

for (int i=0; i<=n; ++i)
   for(int j=0; j<=m; ++j){
       if ( i==0 && j==0 ) A[i][j] = 0;
       else if ( i==0 ) A[i][j] = j;
       else if ( j==0 ) A[i][j] = i;
       else {
          if ( A[i-1][j-1] > A[i-1][j] ){
             A[i][j] = A[i-1][j-1];
             B[i][j] = "D";
          } else {
             A[i][j] = A[i-1][j];
             B[i][j] = "T";
          }
       }
   }

I wonder if there exists some more compact way to code it using Lambda Expressions. Unfortunately, I'm new with Lambdas and I'm not sure this example is suitable for them (or if it's actually convenient to use them here).

I found some solutions that suggest using IntStream, defining the range, and iterating in each element:

IntStream.range(0, n)
         .forEach( (i) -> {
             IntStream.range(0,m)
             .forEach( (j) -> {
                // Body of the function.   
             })
          }); 

But, it seems to be a more complex way to define the for loops.

I'm looking for a way to check the maximum of the two elements (possibly even to check if they're the same, so both are the maximum) and according to which is the max value modifying both Matrices.

Any suggestions?

  • So, you want to populate your two arrays with some numbers, based on their indexes? BTW: Your (first) code does not even compile, does it? – Fabian Damken Mar 05 '16 at 16:01
  • Lambda expressions certainly won't let you assign `String` to elements of an `int` array. – fabian Mar 05 '16 at 16:02
  • @fabian I typed it here without checking, there may be errors (there are indeed). The int matrix is one of them. Yes, I want to populate those two matrices with values/strings according to some relationships between the other cells of the matrix A. –  Mar 05 '16 at 17:35
  • 5
    Not every problem must be solved with streams and lambda expressions. Just keep your loops. – JB Nizet Mar 05 '16 at 17:39
  • @JBNizet Thanks for your answer... I thought so! –  Mar 05 '16 at 18:26

1 Answers1

2

Disclaimer: I strongly believe that the best way is to stick with nested loops.

But, if you are really curious (as I am), here's a way to do the same with streams and a recursive function of 2 variables.

First, define two functions. Function A maps a pair of integers (i, j) to an integer and function B maps a pair of integers (i, j) to a string:

enum Functions {
    INSTANCE;

    public BiFunction<Integer, Integer, Integer> A = (i, j) ->
        i == 0 && j == 0 ? 0 :
        i == 0 ? j :
        j == 0 ? i :
        this.A.apply(i - 1, j - 1) > this.A.apply(i - 1, j) ? this.A.apply(i - 1, j - 1) :
        this.A.apply(i - 1, j);

    public BiFunction<Integer, Integer, String> B = (i, j) ->
        i == 0 || j == 0 ? null :
        this.A.apply(i - 1, j - 1) > this.A.apply(i - 1, j) ? "D" :
        "T";
}

The idea behind these functions is that they mimic the if/else if/else logic inside the nested loop.

Note 1: A is a recursive lambda, while B uses A to get its result.

Note 2: I've also changed the if/else if/else statements to nested ternary operators.

Note 3: Functions is a singleton enum, as suggested by Joshua Bloch (see this answer).

Note 4: To avoid autoboxing, IntBinaryOperator should be used for function A.

Note 5: As A is recursive, it should use automatic memoization to avoid recalculating results when given the same argument values.

Note 6: As B uses A to calculate its results, it should also use a memoized version of A.

Now, you could use the above functions to produce the matrices:

Integer[][] A = IntStream.range(0, n)
    .mapToObj(i -> IntStream.range(0, m)
        .mapToObj(j -> Functions.INSTANCE.A.apply(i, j))
        .toArray(Integer[]::new))
    .toArray(Integer[][]::new);

String[][] B = IntStream.range(0, n)
    .mapToObj(i -> IntStream.range(0, m)
        .mapToObj(j -> Functions.INSTANCE.B.apply(i, j))
        .toArray(String[]::new))
    .toArray(String[][]::new);

Final comment: This is an exercise I did to satisfy my own curiousity and dig into the mysteries of functional programming :) Please don't use this code in production.

Community
  • 1
  • 1
fps
  • 33,623
  • 8
  • 55
  • 110
  • 1
    Why do you advice not to use this code in production? Is there any performance reason for this, or is it only a readability concern? – Dim' Mar 25 '18 at 15:09
  • @Dim' Hi! Because it's using a recursive lambda (function `A` here). Recursive calls use the stack, so if there are many recursive calls you might get a `StackOverflowError`. – fps Mar 25 '18 at 17:08