First, we want to find the locations where the array has a zero next to a non-zero.
rr, cc = np.where((arr[:, 1:] == 0) & (arr[:, :-1] != 0))
Now, we can use np.add.reduceat
to add elements. Unfortunately, reduceat
needs a list of 1-d indices, so we're going to have to play with shapes a little. Calculating the equivalent indices of rr, cc
in a flattened array is easy:
reduce_indices = rr * arr.shape[1] + cc + 1
# array([ 4, 8, 10, 13, 19, 26, 28])
We want to reduce from the start of every row, so we'll create a row_starts
to mix in with the indices calculated above:
row_starts = np.arange(arr.shape[0]) * arr.shape[1]
# array([ 0, 11, 22])
reduce_indices = np.hstack((row_starts, reduce_indices))
reduce_indices.sort()
# array([ 0, 4, 8, 10, 11, 13, 19, 22, 26, 28])
Now, call np.add.reduceat
on the flattened input array, reducing at reduce_indices
totals = np.add.reduceat(arr.flatten(), reduce_indices)
# array([ 7, 9, 3, 0, 3, 12, 0, 25, 1, 1])
Now we have the totals, we need to assign them to an array of zeros. Note that the 0
th element of totals
needs to go to the 1
th index of reduce_indices
, and the last element of totals
is to be discarded:
result_f = np.zeros((arr.size,))
result_f[reduce_indices[1:]] = totals[:-1]
result = result_f.reshape(arr.shape)
Now, one last step remains. For cases where the last element in a row is nonzero, reduceat
would calculate a nonzero value for the first element of the next row, as you mentioned in the comment below. An easy solution is to overwrite these to zero.
result[:, 0] = 0
which gives the expected result:
array([[ 0., 0., 0., 0., 7., 0., 0., 0., 9., 0., 3.],
[ 0., 0., 3., 0., 0., 0., 0., 0., 12., 0., 0.],
[ 0., 0., 0., 0., 25., 0., 1., 0., 0., 0., 0.]])