简体   繁体   中英

Find number of non-zero elements adjacent to zeros in numpy 2D array

Given a matrix, I want to count number of filled elements (non-zero cells) adjacent to empty (zero) cells, where adjacency is along rows (left/right).

I have tried playing around with np.roll and subtracting matrices, but I'm not sure how to code this without loops.

For example, given the matrix:

arr = 
[[1 1 0 0 0 0 0 0 1 0]
 [1 1 0 0 0 0 0 1 1 1]
 [0 1 1 0 0 0 0 0 0 0]
 [0 1 1 0 0 0 0 0 0 0]
 [0 1 1 0 0 0 0 0 0 0]
 [0 0 1 0 0 0 0 0 0 0]
 [0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]]

We have 12 non-zero elements adjacent to zeros.

Approach #1

We can use 2D convolution to solve it with an appropriate kernel ([1,1,1]) or ([1,0,1]) on the zeros mask and look for the convolution summations to be >=1 , which signals at least one zero in each sliding window of three elements and with an additional check of the current element being non-zero confirms that there's at least one neighboring 0 .

The implementation would look something like this -

In [245]: a  # input array
Out[245]: 
array([[1, 1, 0, 0, 0, 0, 0, 0, 1, 0],
       [1, 1, 0, 0, 0, 0, 0, 1, 1, 1],
       [0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
       [0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
       [0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

In [246]: from scipy.signal import convolve2d

In [248]: k = [[1,1,1]] # kernel for convolution

In [249]: ((convolve2d(a==0,k,'same')>=1) & (a!=0)).sum()
Out[249]: 12

Approach #2

Another approach would be making use of slicing as we would look for one-off offsetted elements along each row for the zeros and non-zeros matches for the left and right hand sides LHS and RHS and finally sum those -

maskRHS = (a[:,1:]==0) & (a[:,:-1]!=0)
maskLHS = (a[:,1:]!=0) & (a[:,:-1]==0)
maskRHS[:,1:] |= maskLHS[:,:-1]
out = maskRHS.sum() + maskLHS[:,-1].sum()

You should be able to do something like

ar = a[:,1:] & ~a[:,:-1]
al = a[:,:-1] & ~a[:,1:]
al[:, 0].sum() + (al[:, 1:] | ar[:, :-1]).sum() + ar[:, -1].sum()  # 12

Here, the idea is that ar keeps track of those 1s that are to the right of a 0, while al keeps track of those 1s that are to the left of a 0, and we then take care not to double count.

If you don't mind working with the transpose instead, the entire thing becomes a bit more concise:

b = a.T
br = b[1:] & ~b[:-1]
bl = b[:-1] & ~b[1:]
bl[0].sum() + (bl[1:] | br[:-1]).sum() + br[-1].sum()  # Also 12

A perhaps more readable version that avoids having to handle the two edges separately would be to extend both of b[1:] and b[:-1] with a column of ones:

edge = np.ones(9, int)
(b & ~(np.vstack([b[1:], edge]) & np.vstack([edge, b[:-1]]))).sum()  # Also 12

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM