If you just want to use numpy
, my way is to find out the neighbors of all true values in the original array, the calculation method is to judge whether the Chebyshev distance (L-infinite distance) between the position of elements in the array and the position of true values is 1, then merge them with logical or operation:
>>> ar = np.array([[0,0,0,1,0],
... [0,0,0,0,0],
... [0,1,0,0,0],
... [0,0,0,1,0],
... [0,0,0,0,0]], bool)
>>> row, col = ar.nonzero()
>>> rows, cols = np.indices(ar.shape)
>>> np.logical_or.reduce([np.maximum(np.abs(rows - i), np.abs(cols - j)) == 1 for i, j in zip(row, col)])
array([[False, False, True, False, True],
[ True, True, True, True, True],
[ True, False, True, True, True],
[ True, True, True, False, True],
[False, False, True, True, True]])
By broadcasting, you can also avoid the list comprehension:
>>> rows, cols = np.indices(ar.shape, sparse=True) # Setting to sparse does not affect the calculation.
>>> i = np.abs(rows[None] - row[:, None, None])
>>> j = np.abs(cols[None] - col[:, None, None])
>>> np.logical_or.reduce(np.maximum(i, j) == 1)
array([[False, False, True, False, True],
[ True, True, True, True, True],
[ True, False, True, True, True],
[ True, True, True, False, True],
[False, False, True, True, True]])
To make the code look shorter, I used None
instead of np.newaxis
, you can use the latter for readability.
After testing, even with the help of broadcasting, it is slower than the second answer of @d.b , but it is not too bad:
>>> def loop_reduce(ar):
... row, col = ar.nonzero()
... rows, cols = np.indices(ar.shape)
... return np.logical_or.reduce([np.maximum(np.abs(rows - i), np.abs(cols - j)) == 1 for i, j in zip(row, col)])
...
>>> def broadcast_reduce(ar):
... row, col = ar.nonzero()
... rows, cols = np.indices(ar.shape, sparse=True)
... i = np.abs(rows[None] - row[:, None, None])
... j = np.abs(cols[None] - col[:, None, None])
... return np.logical_or.reduce(np.maximum(i, j) == 1)
...
>>> def max_filter(ar):
... ans = maximum_filter(ar, size=(3, 3))
... ans[ar] = False
... return ans
...
>>> timeit(lambda: loop_reduce(ar), number=10000)
0.3127206000208389
>>> timeit(lambda: broadcast_reduce(ar), number=10000)
0.15011020001838915
>>> timeit(lambda: max_filter(ar), number=10000)
0.12893440001062118
At least this can be a way for you to solve similar problems in the future :-)
* Be the first to Make Comment