I have a matrix eg.
[[4,5,0,0,0],
[5,1,2,1,0],
[0,2,3,2,0],
[0,1,2,1,0],
[0,0,0,0,0]]
For each element in the matrix, I am trying to get the sum of its adjacent diagonal elements and the sum of its adjacent horizontal and vertical elements.
Taking the 3 in the middle of the matrix for example, I am trying to calculate the sum of the diagonally adjacent elements (the 1's) and the horizontally and vertically adjacent elements (the 2's). For corner and edge cases, I want to disregard the areas where there are no elements eg.(for the 4 in the upper left, I'd want to get the sum of the diagonally adjacent 1 and the sum of the horizontally and vertically adjacent 5's.
What would be the most efficient way to do this in python?
So far i've come up with
diagonals = lambda x,y:[(x-1, y-1), (x-1,y+1), (x+1,y-1), (x+1,y+1)]
horiz_vert= lambda x,y:[(x,y+1), (x,y-1), (x+1,y), (x-1,y)]
to get the indices but these don't take into account the edge and corner cases.
The right tool to solve this task appears to be convolution. You only need to define the filters that will be applied in each position ("sum of diagonal neighbors" or "sum of vertical/horizontal neighbors") and you're done.
Here's how the filters look like:
You can think of 2D convolution as of shifting the filter across your matrix and calculating in each position the sum of the element-wise multiplication. Strictly speaking, the filters need to be mirrored, but in your case they are symmetric anyway. Here's a random illustration of the principle as found on the web:
The only difference to your case is that we want to place the filters everywhere, including the "boundary" or "corner" positions. This can be achieved by zero-padding the original matrix
D
such that the center of the filter can be placed e.g. in position(0,0)
.Good news! It's already implemented in Numpy/Scipy and it's a very efficient implementation! Now you just construct a matrix by convolving
D
with a filterh
.From these matrices you can read off the required sums in each position. E.g. the central element in your original matrix
D
, i.e.D[2,2]
is surrounded by 4 ones on the diagonals and 4 twos on the horizontal/vertical adjacency. The entries in the convolution output at the position(2,2)
are therefore4
and8
. The positionD[0,0]
has only one diagonal neighbor (1
) and two horizontal/vertical neighbors (5
and5
). The entries in the convolution output matrices are as expected1
and10
.As it turns out, I realized there's a much simpler way to do this than described in my previous answer.
Results in:
Ignoring the corner cases for a moment, one way to do this is to overlay shifted versions of the matrices atop one another.
For instance, given a matrix
m
, we sum over the rows:Running this on your matrix, we obtain:
We can then do something similar for the columns. For speed, I suggest you specialize the code I wrote for rows, but I'm going to be lazy and do it on the transpose:
Running this on your matrix, we obtain:
Now we need to combine these matrices together. Let's shave off the extra rows/columns to produce 3x3 matrices, then sum them together:
Just as desired!
...But how can we account for the boundaries? Simply pad your matrix with zeroes!
Which gives:
To sum diagonals, it's simply:
Which produces:
And now apply the same principle for the sum of adjacent numbers.
If you need the sum of the diagonals and verticals: