1. Overview
Working with two-dimensional arrays (2D arrays) is common in Java, especially for tasks that involve matrix operations. One such task is calculating the sum of the diagonal values in a 2D array.
In this tutorial, we’ll explore different approaches to summing the values of the main and secondary diagonals in a 2D array.
2. Introduction to the Problem
First, let’s quickly understand the problem.
A 2D array forms a matrix. As we need to sum the elements on the diagonals, we assume the matrix is n x n, for example, a 4 x 4 2D array:
static final int[][] MATRIX = new int[][] {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 100 }
};
Next, let’s clarify what we mean by the main diagonal and the secondary diagonal:
- Main Diagonal – The diagonal runs from the top-left to the bottom-right of the matrix. For example, in the example above, the elements on the main diagonal are 1, 6, 11, and 100
- Secondary Diagonal – The diagonal runs from the top-right to the bottom-left. In the same example, 4, 7, 10, and 13 belong to the secondary diagonal.
The sum of both diagonal values are following:
static final int SUM_MAIN_DIAGONAL = 118; //1+6+11+100
static final int SUM_SECONDARY_DIAGONAL = 34; //4+7+10+13
Since we want to create methods to cover both diagonal types, let’s create an Enum for them:
enum DiagonalType {
Main, Secondary
}
Later, we can pass a DiagonalType to our solution method to get the corresponding result.
3. Identifying Elements on a Diagonal
To calculate the sum of diagonal values, we must first identify those elements on a diagonal. In the main diagonal case, it’s pretty straightforward. When an element’s row-index (rowIdx) and column-index (colIdx) are equal, the element is on the main diagonal, such as MATRIX[0][0] = 1, MATRIX[1][1] = 6, and MATRIX[3][3] = 100.
On the other hand, given a n x n matrix, if an element is on the secondary diagonal, we have rowIdx + colIdx = n – 1. For instance, in our 4 x 4 matrix example*, MATRIX[0][3] = 4 (0 + 3 = 4 -1), MATRIX[1][2] = 7 (1 + 2 = 4 – 1),* and MATRIX[3][0] = 13 (3 + 0 = 4 -1 ). So, we have colIdx = n – rowIdx – 1.
Now that we understand the rule of diagonal elements, let’s create methods to calculate the sums.
4. The Loop Approach
A straightforward approach is looping through row indexes, depending on the required diagonal type, summing the elements:
int diagonalSumBySingleLoop(int[][] matrix, DiagonalType diagonalType) {
int sum = 0;
int n = matrix.length;
for (int rowIdx = 0; rowIdx < n; row++) {
int colIdx = diagonalType == Main ? rowIdx : n - rowIdx - 1;
sum += matrix[rowIdx][colIdx];
}
return sum;
}
As we can see in the implementation above, we calculate the required colIdx depending on the given diagonalType, and then add the element on rowIdx and colIdx to the sum variable.
Next, let’s test whether this solution produces the expected results:
assertEquals(SUM_MAIN_DIAGONAL, diagonalSumBySingleLoop(MATRIX, Main));
assertEquals(SUM_SECONDARY_DIAGONAL, diagonalSumBySingleLoop(MATRIX, Secondary));
It turns out this method sums correct values for both diagonal types.
5. DiagonalType with an IntBinaryOperator Object
The loop-based solution is straightforward. However, in each loop step, we must check the diagonalType instance to determine colIdx, although diagonalType is a parameter that won’t change during the loop.
Next, let’s see if we can improve it a bit.
One idea is to assign each DiagonalType instance an IntBinaryOperator object so that we can calculate colIdx without checking which diagonal type we have:
enum DiagonalType {
Main((rowIdx, len) -> rowIdx),
Secondary((rowIdx, len) -> (len - rowIdx - 1));
public final IntBinaryOperator colIdxOp;
DiagonalType(IntBinaryOperator colIdxOp) {
this.colIdxOp = colIdxOp;
}
}
As the code above shows, we added an IntBinaryOperator property to the DiagonalType Enum. IntBinaryOperation is a functional interface that takes two int arguments and produces an int result. In this example, we use two lambda expressions as the Enum instances’ IntBinaryOperator objects.
Now, we can remove the ternary operation of the diagonal type checking in the for loop:
int diagonalSumFunctional(int[][] matrix, DiagonalType diagonalType) {
int sum = 0;
int n = matrix.length;
for (int rowIdx = 0; rowIdx < n; row++) {
sum += matrix[rowIdx][diagonalType.colIdxOp.applyAsInt(rowIdx, n)];
}
return sum;
}
As we can see, *we can directly invoke diagonalType’s colIdxOp function by calling applyAsInt() to get the required* colIdx.
Of course, the test still passes:
assertEquals(SUM_MAIN_DIAGONAL, diagonalSumFunctional(MATRIX, Main));
assertEquals(SUM_SECONDARY_DIAGONAL, diagonalSumFunctional(MATRIX, Secondary));
6. Using Stream API
Functional interfaces were introduced in Java 8. Another significant feature Java 8 brought is Stream API. Next, let’s solve the problem using these two Java 8 features:
public int diagonalSumFunctionalByStream(int[][] matrix, DiagonalType diagonalType) {
int n = matrix.length;
return IntStream.range(0, n)
.map(i -> MATRIX[i][diagonalType.colIdxOp.applyAsInt(i, n)])
.sum();
}
In this example, we replace the for-loop with IntStream.range(). Also, map() is responsible for transforming each index (i) to the required elements on the diagonal. Then, sum() produces the result.
Finally, this solution passes the test as well:
assertEquals(SUM_MAIN_DIAGONAL, diagonalSumFunctionalByStream(MATRIX, Main));
assertEquals(SUM_SECONDARY_DIAGONAL, diagonalSumFunctionalByStream(MATRIX, Secondary));
This approach is fluent and easier to read than the initial loop-based solution.
7. Conclusion
In this article, we’ve explored different ways to calculate the sum of diagonal values in a 2D Java array. Understanding the indexing for the main and secondary diagonals is the key to solving the problem.
As always, the complete source code for the examples is available over on GitHub.