1

This is an implementation of the 0-1 Knapsack Problem. The problem statement goes like this,

You're given two arrays, one containing the weights of a set of items and the other containing values for the respective weights. You're provided a max weight. Within the constraint of staying under the max weight, determine the maximum value that you can obtain by selecting or not selecting the set of items. The values and the weight list will always be of the same size.

This is my solution, which generally works fine(not on edge cases).

  public static int getCombination(int[] weights, int[] values, int maxWeight){

        int[][] memo = new int[weights.length][maxWeight + 1];
        for (int i = 0; i < memo.length; i++) {
          for(int j=0; j < memo[i].length; j++){
            if(j == 0){
              memo[i][j] = 0;
            }else if(weights[i] > j){
              if(i == 0) memo[i][j] = 0;
              else memo[i][j] = memo[i-1][j];
            }else{
              if(i == 0){
                memo[i][j] = values[i];
              }
              else{
                memo[i][j] = Integer.max((values[i] + memo[i-1][j- weights[i]]), memo[i-1][j]);
              }
            }
          }
        }
        return memo[weights.length -1][maxWeight];
      }

Now I want to re-write this complete logic in a declarative manner using Java 8 Streams and lambdas. Can someone help me with that.

2
  • 1
    sure. what did u try? Commented May 15, 2017 at 20:55
  • I tried iterating the loops using flatMaps, but that doesn't really help as I'm struggling to access the index values is filling the memo. In crux, I don't really know how to get started. Commented May 15, 2017 at 21:04

2 Answers 2

3

Since your for loop based solution is completely fine, streams do not add much value here if you only convert the for loops to forEach streams.

You get more out of using streams, if you use IntStream and the toArray method, because you can concentrate on calculating the value based on row and column index, and not care about filling it into the array.

int[][] memo = IntStream.range(0, rows)
                        .mapToObj(r -> IntStream.range(0, cols)
                                                .map(c -> computeValue(r, c))
                                                .toArray())
                        .toArray(int[rows][cols]::new);

Here, we create an array for each row, and then put those into a 2D-array at the end. As you can see, the toArray() methods take care of filling the arrays.


Actually, now that I looked at your method to calculate the values more closely, I realize that streams might be difficult if not impossible to use in this case. The problem is that you need values from previous columns and rows to calculate the current value. This is not possible in my solution precisely because we only create the arrays at the end. More specifically, my approach is stateless, i.e. you do not remember the result of previous iterations.

You could see if you can use Stream.reduce() to achieve your goal instead.

Sign up to request clarification or add additional context in comments.

Comments

2

BTW, your approach is fine. If you don't want to parallelize this, you are good to go.

Here's a possible starting point to create the indices into your array:

        int rows = 3;
        int cols = 4;
        int[][] memo = new int[rows][cols];
        IntStream.range(0, rows * cols).forEach(n -> {
            int i = n / cols;
            int j = n % cols;

            System.out.println("(" + i + "," + j + ")");
        });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.