Iterating a 2D Array using Java 8 - java

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.

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.

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 + ")");
});

Related

How to find most profitable Path in 2-Dimensional Array

I'm trying to implement a game where the viable moves are down-left and down-right.
The parameter for the function is for the size of the array, so if you pass 4 it will be a 4 by 4 array.
The starting position is the top row from any column. Every element in the array is a number in the range 1-100, taken from a file. I need to find the resulting value for the most profitable route from any starting column.
My current implementation will compare the right position and left position and move to whichever is higher. The problem is, for example, if the left position is lower in value than the right, but the left position will provide more profit in the long run since it can access higher value elements, my algorithm fails.
Here is a demo:
84 (53) 40 62
*42* 14 [41] 57
76 *47* 80 [95]
If we start at number 53. The numbers enclosed in * are the moves that my algorithm will take, but the numbers enclosed in [] are the moves my algorithm should take.
This is my code:
import java.util.ArrayList;
import java.util.Scanner;
public class bestPathGame{
private int[][] grid;
private int n;
public bestPathGame(int num){
Scanner input = new Scanner(System.in);
n = num;
grid = new int[n][n];
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
grid[i][j] = input.nextInt();
}
}
}
public static void main(String[] args){
bestPathGame obj = new bestPathGame(Integer.parseInt(args[0]));
obj.bestPath();
}
private boolean moveLeftBetter(int r,int c){
if(c <= 0){
return false;
} else if (c >= n -1 ){
return true;
}
return grid[r][c-1] > grid[r][c+1];
}
public void bestPath(){
ArrayList<Integer> allOptions = new ArrayList<>();
for(int k = 0; k < n; k++){
int row = 0;
int col = k;
int collection = grid[row][col];
while(row < n - 1){
row += 1;
if(moveLeftBetter(row,col)){
col-=1;
} else{
col+=1;
}
collection += grid[row][col];
}
allOptions.add(collection);
}
System.out.println(allOptions.stream().reduce((a,b)->Integer.max(a,b)).get());
}
}
Greedy algorithm vs Dynamic programming
There's an issue with the logic of your solution.
Basically, what you are implemented is a called a greedy algorithm. At each step of iteration, you are picking a result that optimal locally, assuming that this choice will lead to the optimal global result. I.e. your code is based on the assumption that by choosing a local maximum between the two columns, you will get the correct global maximum.
As a consequence, your code in the bestPath() method almost at each iteration will discard a branch of paths based on only one next value. This approach might lead to incorrect results, especially with large matrixes.
Greedy algorithms are rarely able to give an accurate output, usually their result is somewhat close but not precise. As an upper-hand, they run fast, typically in O(n) time.
For this problem, you need to use a dynamic programming (DP).
In short, DP is an enhanced brute-force approach which cashes the results and reuses them instead of recalculating the same values multiple times. And as well, as a regular brute-force DP algorithms are always checking all possible combinations.
There are two major approaches in dynamic programming: tabulation and memoization (take a look at this post for more information).
Tabulation
While implementing a tabulation first you need to create an array which then need to be prepopulated (completely or partially). Tabulation is also called the bottom-up approach because calculation start from the elementary edge cases. Every possible outcome is being computed based on the previously obtained values while iterating over this array. The final result will usually be stored in the last cell (in this case in the last row).
To implement the tabulation, we need to create the matrix of the same size, and copy all the values from the given matrix into it. Then row by row every cell will be populated with the maximum possible profit that could be obtained by reaching this cell from the first row.
I.e. every iteration will produce a solution for a 2D-array, that continuously increases by one row at each step. It'll start from the array that consists of only one first row (no changes are needed), then to get the profit for every cell in the second row it's values has to be combined with the best values from the first row (that will be a valid solution for 2D-array of size 2 * n), and so on. That way, solution gradually develops, and the last row will contain the maximum results for every cell.
That how the code will look like:
public static int getMaxProfitTabulation(int[][] matrix) {
int[][] tab = new int[matrix.length][matrix.length];
for (int row = 0; row < tab.length; row++) { // populating the tab to preserve the matrix intact
tab[row] = Arrays.copyOf(matrix[row], matrix[row].length);
}
for (int row = 1; row < tab.length; row++) {
for (int col = 0; col < tab[row].length; col++) {
if (col == 0) { // index on the left is invalid
tab[row][col] += tab[row - 1][col + 1];
} else if (col == matrix[row].length - 1) { // index on the right is invalid
tab[row][col] += tab[row - 1][col - 1];
} else {
tab[row][col] += Math.max(tab[row - 1][col - 1], tab[row - 1][col + 1]); // max between left and right
}
}
}
return getMax(tab);
}
Helper method responsible for extracting the maximum value from the last row (if you want to utilize streams for that, use IntStream.of(tab[tab.length - 1]).max().orElse(-1);).
public static int getMax(int[][] tab) {
int result = -1;
for (int col = 0; col < tab[tab.length - 1].length; col++) {
result = Math.max(tab[tab.length - 1][col], result);
}
return result;
}
Memoization
The second option is to use Memoization, also called the top-down approach.
As I said, DP is an improved brute-force algorithm and memoization is based on the recursive solution that generates all possible outcomes, that is enhanced by adding a HashMap that stores all previously calculated results for every cell (i.e. previously encountered unique combination of row and column).
Recursion starts with the first row and the base-case of recursion (condition that terminates the recursion and is represented by a simple edge-case for which output is known in advance) for this task is when the recursive call hits the last row row == matrix.length - 1.
Otherwise, HashMap will be checked whether it already contains a result. And if it not the case all possible combination will be evaluated and the best result will be placed into the HashMap in order to be reused, and only the then the method returns.
Note that tabulation is usually preferred over memoization, because recursion has significant limitations, especially in Java. But recursive solutions are sometimes easier to came up with, so it's completely OK to use it when you need to test the idea or to prove that an iterative solution is working correctly.
The implementation will look like that.
public static int getMaxProfitMemoization(int[][] matrix) {
int result = 0;
for (int i = 0; i < matrix[0].length; i++) {
result = Math.max(result, maxProfitHelper(matrix, 0, i, new HashMap<>()));
}
return result;
}
public static int maxProfitHelper(int[][] matrix, int row, int col,
Map<String, Integer> memo) {
if (row == matrix.length - 1) { // base case
return matrix[row][col];
}
String key = getKey(row, col);
if (memo.containsKey(key)) { // if cell was already encountered result will be reused
return memo.get(key);
}
int result = matrix[row][col]; // otherwise result needs to be calculated
if (col == matrix[row].length - 1) { // index on the right is invalid
result += maxProfitHelper(matrix, row + 1, col - 1, memo);
} else if (col == 0) { // index on the left is invalid
result += maxProfitHelper(matrix, row + 1, col + 1, memo);
} else {
result += Math.max(maxProfitHelper(matrix, row + 1, col - 1, memo),
maxProfitHelper(matrix, row + 1, col + 1, memo));
}
memo.put(key, result); // placing result in the map
return memo.get(key);
}
public static String getKey(int row, int col) {
return row + " " + col;
}
Method main() and a matrix-generator used for testing purposes.
public static void main(String[] args) {
int[][] matrix = generateMatrix(100, new Random());
System.out.println("Tabulation: " + getMaxProfitTabulation(matrix));
System.out.println("Memoization: " + getMaxProfitMemoization(matrix));
}
public static int[][] generateMatrix(int size, Random random) {
int[][] result = new int[size][size];
for (int row = 0; row < result.length; row++) {
for (int col = 0; col < result[row].length; col++) {
result[row][col] = random.nextInt(1, 101);
}
}
return result;
}

Smallest element in largest row

I came across this problem in class and I'm stuck on it. I did plenty of research but I'm not being able to fix my code.
I need to create a matrix and find the smallest value in the row of the largest value (I believe this element is called minimax). I'm trying to do with a simple 3 x 3 matrix. What I have so far:
Scanner val = new Scanner(System.in);
int matrizVal[][] = new int[3][3];
for (int a = 0; a < matrizVal.length; a++) {
for (int b = 0; b < matrizVal.length; b++) {
System.out.print("(" + a + ", " + b + "): ");
matrizVal[a][b] = val.nextInt();
}
}
int largest = matrizVal[0][0];
int largestrow = 0;
int arr[] = new int[2];
for (int row = 0; row < matrizVal.length; row++){
for (int col = 0; col < matrizVal.length; col++){
if (largest < matrizVal[row][col]){
largest = matrizVal[row][col];
largestrow = row;
}
}
}
To find the so called minimax element I decided to create a for each loop and get all the values of largestrow except the largest one.
for (int i : matrizVal[largestrow]){
if (i != largest){
System.out.print(i);
}
}
Here's where I'm stuck! I'd simply like to 'sort' this integer and take the first value and that'd be the minimax. I'm thinking about creating an array of size [matrizVal.length - 1], but not sure if it's gonna work.
I did a lot of research on the subject but nothing seems to help. Any tips are welcome.
(I don't think it is but I apologize if it's a duplicate)
Given the code you have provided, matrizVal[largestrow] should be the row of the matrix that contains the highest valued element.
Given that your task is to extract the smallest value in this array, there are a number of options.
If you want to simply extract the minimum value, a naive approach would go similarly to how you determined the maximum value, just with one less dimension.
For example:
int min = matrizVal[largestrow][0];
for (int i = 0; i < matrizVal.length; i++) {
if (matrizVal[largestrow][i] < min) {
min = matrizVal[largestrow][i];
}
}
// min will be the target value
Alternatively, if you want to sort the array such that the first element of the array is always the smallest, first ensure that you're making a copy of the array so as to avoid mutating the original matrix. Then feel free to use any sorting algorithm of your choice. Arrays.sort() should probably suffice.
You can simplify your approach by scanning each row for the maximum and minimum values in that row and then deciding what to do with those values based on the maximum value found in previous rows. Something like this (untested) should work:
int largestValue = Integer.MIN_VALUE;
int smallestValue = 0; // anything, really
for (int[] row : matrizVal) {
// First find the largest and smallest value for this row
int largestRowValue = Integer.MIN_VALUE;
int smallestRowValue = Integer.MAX_VALUE;
for (int val : row) {
smallestRowValue = Math.min(smallestRowValue, val);
largestRowValue = Math.max(largestRowValue, val);
}
// now check whether we found a new highest value
if (largestRowValue > largestValue) {
largestValue = largestRowValue;
smallestValue = smallestRowValue;
}
}
This doesn't record the row index, since it didn't sound like you needed to find that. If you do, then replace the outer enhanced for loop with a loops that uses an explicit index (as with your current code) and record the index as well.
I wouldn't bother with any sorting, since that (1) destroys the order of the original data (or introduces the expense of making a copy) and (2) has higher complexity than a one-time scan through the data.
You may want to consider a different alternative using Java 8 Stream :
int[] maxRow = Arrays.stream(matrizVal).max(getCompertator()).get();
int minValue = Arrays.stream(maxRow).min().getAsInt();
where getCompertator() is defined by:
private static Comparator<? super int[]> getCompertator() {
return (a1, a2)->
Integer.compare(Arrays.stream(a1).max().getAsInt(),
Arrays.stream(a2).max().getAsInt()) ;
}
Note that it may not give you the (undefined) desired output if two rows include the same highest value .

Two sum - Doesn't work

Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
Consider input [3,2,4] and target is 6. I added (3,0) and (2,1) to the map and when I come to 4 and calculate value as 6 - 4 as 2 and when I check if 2 is a key present in map or not, it does not go in if loop.
I should get output as [1,2] which are the indices for 2 and 4 respectively
public int[] twoSum(int[] nums, int target) {
int len = nums.length;
int[] arr = new int[2];
Map<Integer,Integer> map = new HashMap<Integer,Integer>();
for(int i = 0;i < len; i++)
{
int value = nums[i] - target;
if(map.containsKey(value))
{
System.out.println("Hello");
arr[0] = value;
arr[1] = map.get(value);
return arr;
}
else
{
map.put(nums[i],i);
}
}
return null;
}
I don't get where the problem is, please help me out
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution, and you may not use the same element twice. Consider input [3,2,4] and target is 6. I added (3,0) and (2,1) to the map and when I come to 4 and calculate value as 6 - 4 as 2 and when I check if 2 is a key present in map or not, it does not go in if loop.
Okay, let's take a step back for a second.
You have a list of values, [3,2,4]. You need to know which two will add up 6, well, by looking at it we know that the answer should be [1,2] (values 2 and 4)
The question now is, how do you do that programmatically
The solution is (to be honest), very simple, you need two loops, this allows you to compare each element in the list with every other element in the list
for (int outter = 0; outter < values.length; outter++) {
int outterValue = values[outter];
for (int inner = 0; inner < values.length; inner++) {
if (inner != outter) { // Don't want to compare the same index
int innerValue = values[inner];
if (innerValue + outterValue == targetValue) {
// The outter and inner indices now form the answer
}
}
}
}
While not highly efficient (yes, it would be easy to optimise the inner loop, but given the OP's current attempt, I forewent it), this is VERY simple example of how you might achieve what is actually a very common problem
int value = nums[i] - target;
Your subtraction is backwards, as nums[i] is probably smaller than target. So value is getting set to a negative number. The following would be better:
int value = target - nums[i];
(Fixing this won't fix your whole program, but it explains why you're getting the behavior that you are.)
This code for twoSum might help you. For the inputs of integer array, it will return the indices of the array if the sum of the values = target.
public static int[] twoSum(int[] nums, int target) {
int[] indices = new int[2];
outerloop:
for(int i = 0; i < nums.length; i++){
for(int j = 0; j < nums.length; j++){
if((nums[i]+nums[j]) == target){
indices[0] = i;
indices[1] = j;
break outerloop;
}
}
}
return indices;
}
You can call the function using
int[] num = {1,2,3};
int[] out = twoSum(num,4);
System.out.println(out[0]);
System.out.println(out[1]);
Output:
0
2
You should update the way you compute for the value as follows:
int value = target - nums[i];
You can also check this video if you want to better visualize it. It includes Brute force and Linear approach:

Time complexity on iterative and recursive solution

I'm trying to solve the following problem:
I feel like I've given it a lot of thoughts and tried a lot of stuff. I manage to solve it, and produce correct values but the problem is that it isn't time efficient enough. It completes 2 out of the Kattis tests and fails on the 3 because of the time limit 1 second was exceeded. There is noway for me to see what the input was that they tested with I'm afraid.
I started out with a recursive solution and finished that. But then I realised that it wasn't time efficient enough so I instead tried to switch to an iterative solution.
I start with reading input and add those to an ArrayList. And then I call the following method with target as 1000.
public static int getCorrectWeight(List<Integer> platesArr, int target) {
/* Creates two lists, one for storing completed values after each iteration,
one for storing new values during iteration. */
List<Integer> vals = new ArrayList<>();
List<Integer> newVals = new ArrayList<>();
// Inserts 0 as a first value so that we can start the first iteration.
int best = 0;
vals.add(best);
for(int i=0; i < platesArr.size(); i++) {
for(int j=0; j < vals.size(); j++) {
int newVal = vals.get(j) + platesArr.get(i);
if (newVal <= target) {
newVals.add(newVal);
if (newVal > best) {
best = newVal;
}
} else if ((Math.abs(target-newVal) < Math.abs(target-best)) || (Math.abs(target-newVal) == Math.abs(target-best) && newVal > best)) {
best = newVal;
}
}
vals.addAll(newVals);
}
return best;
}
My question is, is there some way that I can reduce the time complexity on this one for large number of data?
The main problem is that the size of vals and newVals can grow very quickly, as each iteration can double their size. You only need to store 1000 or so values which should be manageable. You're limiting the values but because they're stored in an ArrayList, it ends up with a lot of duplicate values.
If instead, you used a HashSet, then it should help the efficiency a lot.
You only need to store a DP table of size 2001 (0 to 2000)
Let dp[i] represent if it is possible to form ikg of weights. If the weight goes over the array bounds, ignore it.
For example:
dp[0] = 1;
for (int i = 0; i < values.size(); i++){
for (int j = 2000; j >= values[i]; j--){
dp[j] = max(dp[j],dp[j-values[i]);
}
}
Here, values is where all the original weights are stored. All values of dp are to be set to 0 except for dp[0].
Then, check 1000 if it is possible to make it. If not, check 999 and 1001 and so on.
This should run in O(1000n + 2000) time, since n is at most 1000 this should run in time.
By the way, this is a modified knapsack algorithm, you might want to look up some other variants.
If you think too generally about this type of problem, you may think you have to check all possible combinations of input (each weight can be included or excluded), giving you 2n combinations to test if you have n inputs. This is, however, rather beside the point. Rather, the key here is that all weights are integers, and that the goal is 1000.
Let's examine corner cases first, because that limits the search space.
If all weights are >= 1000, pick the smallest.
If there is at least one weight < 1000, that is always better than any weight >= 2000, so you can ignore any weight >= 1000 for combination purposes.
Then, apply dynamic programming. Keep a set (you got HashSet as suggestion from other poster, but BitSet is even better since the maximum value in it is so small) of all combinations of the first k inputs, and increase k by combining all previous solutions with the k+1'th input.
When you have considered all possibilities, just search the bit vector for the best response.
static int count() {
int[] weights = new int[]{900, 500, 498, 4};
// Check for corner case to limit search later
int min = Integer.MAX_VALUE;
for (int weight : weights) min = Math.min(min, weight);
if (min >= 1000) {
return min;
}
// Get all interesting combinations
BitSet combos = new BitSet();
for (int weight : weights) {
if (weight < 1000) {
for (int t = combos.previousSetBit(2000 - weight) ; t >= 0; t = combos.previousSetBit(t-1)) {
combos.set(weight + t);
}
combos.set(weight);
}
}
// Pick best combo
for (int distance = 0; distance <= 1000; distance++) {
if (combos.get(1000 + distance)) {
return 1000 + distance;
}
if (combos.get(1000 - distance)) {
return 1000 - distance;
}
}
return 0;
}

Java Vectors: how to quickly "symmetrify" a large chunk of a huge sparse matrix

I have a huge sparse matrix (about 500K x 500K entries, with approximately 1% of the values being non-zero.
I'm using #mikera's Vectorz library.
t is a SparseRowMatrix composed of SparseIndexedVector rows.
For this chunk of the matrix, I am computing weights for (i,j) where j>i, putting them into an array of double, then creating the SparseIndexedVector for the row from that array. I was trying to cache the weights so that for the parts of the row where j<i, I could look up the previously computed value for (j,i) and put that value in for (i,j), but that took too much memory. So I am now trying to basically just compute and fill in the upper triangle for that chunk of the matrix, and then "symmetrify" it later. The chunk is from n1 x n1 to n2 x n2 (where n2 - n1 =~ 100K).
Conceptually, this is what I need to do:
for (int i = n1; i < n2; i++) {
for (int j = i + 1; j < n2; j++) {
double w = t.get(i, j);
if (w > 0) {
t.set(j, i, w);
}
}
}
But the "random access" get and set operations are quite slow. I assume unsafeGet would be faster.
Would it improve my performance to do the j-loop as my outer loop and convert the row back to a double array, then add elements and then create a new SparseIndexedVector from that array and replaceRow it back in? Something like:
for (j = n1 + 1; j < n2; j++) {
double[] jRowData = t.getRow(j).asDoubleArray();
for (i = 1; i < j-1; i++) {
double w = t.unsafeGet(i,j);
if (w > 0) {
jRowData[i] = w;
}
}
SparseIndexedVector jRowVector = SparseIndexedVector.createLength(n);
jRowVector.setElements(jRowData);
t.replaceRow(j, jRowVector);
}
Would something like that likely be more efficient? (I haven't tried it yet, as testing things on such large arrays takes a long time, so I'm trying to get an idea of what is "likely" to work well first. I've tried various incarnations on a smaller array (1K x 1K), but I've found that what is faster on a small array is not necessarily the same as what is faster on a large array.)
Is there another approach I should take instead?
Also, since memory is also a large concern for me, would it be helpful at the end of the outer loop to release the array memory explicitly? Can I do that by adding jRowData = null;? I assume that would save time on GC but I'm not all that clear on how memory management works in Java (7 if it matters).
Thanks in advance for any suggestions you can provide.
A matrix is symmetric if m[x,y] is equal to m[y,x] for all x and y, so if you know that the matrix must be symmetric, storing both m[x,y] and m[y,x] is redundant.
You can avoid storing both by rearranging the inputs if they meet a certain condition:
void set(int row, int col, double value) {
if(col < row) {
//call set again with transposed col/row
return set(col, row, value);
//we now know that we're in the top half of the matrix, proceed like normal
...
}
Do something similar for the get method:
double get(int row, int col) {
if(col < row) {
//call get again with transposed col/row
return get(col, row);
//we now know that we're in the top half of the matrix, proceed like normal
...
return value;
}
This technique will allow you to both avoid storing redundant values, and force the matrix to be symmetric without having to do any extra processing.
The la4j library guarantees O(log n) (where the n is a dimension) performance for both get/set operations on sparse matrices like CRSMatrix or CCSMatrix. You can perform a small experiment - just compile your code from conceptual box and run it with la4j:
for (int i = n1; i < n2; i++) {
for (int j = i + 1; j < n2; j++) {
double w = t.get(i, j);
if (w > 0) {
t.set(j, i, w);
}
}
}
You said you have to handle 500K x 500K matrices. For this size you can expect log_2(500 000) ~ 18 internal binary search iterations will be performed at each call to get/set operations.
Just think about 18 loop iterations on a moderl JVM and modern CPU.
Hope this helps. Have fun.

Categories

Resources