Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - Finding indices of the first non-zero element to the left for each element

I know there are lots of similar questions, but this one is sightly different.

Given any row such as row = [1 0 0 1 0 1 0 1] I want to output a row that says output = [0 -1 -1 0 -1 3 -1 5]

and basically this is saying, the first 1, points to itself. The 2nd 1, which has index 3, points to the 1 to its left and since that 1 has an index 0, it is 0. The 3rd 1 points to the 1 to it's left, which has index 3. And lastly, the 4th 1 points to the first one to it's left which has index 5. Lastly, all the 0s are set to -1.

I am able to get the indices of the where all the 1 are using numpy.nonzero(row) but I'm not sure how to space out these indices in the same dimensions as input array.

like image 290
Jonathan Avatar asked Oct 24 '25 02:10

Jonathan


2 Answers

It's rather easy if you iterate on the list (using enumerate to keep track of the element index too) and store the last index where 1 (below any non-zero value) was seen (except for the first time)

row = [1, 0, 0, 1, 0, 1, 0, 1]

prev_index = None

result = []
for i,v in enumerate(row):
    if v:
        result.append(i if prev_index is None else prev_index)
        prev_index = i
    else:
        result.append(-1)

>>> result
[0, -1, -1, 0, -1, 3, -1, 5]

This is difficult to achieve with a list comprehension because of the need to store the previous index.

like image 167
Jean-François Fabre Avatar answered Oct 26 '25 16:10

Jean-François Fabre


Basically what we want is to replace all 0s with -1, and all non-zeros with the index of the previous zero, if I understood it correctly.

We can thus create an array of -1s with the same length as the given array, and then replace a view of zeros with the result of np.where:

outp = np.full(a.shape, -1)
idxs = a.nonzero()
if len(idxs) > 0:
    outp[idxs] = np.concatenate(([0], idxs[0][:-1]))

For example:

>>> a = np.array([1, 0, 0, 1, 0, 1, 0, 1])
>>> outp = np.full(a.shape, -1)
>>> idxs = a.nonzero()
>>> outp[idxs] = np.concatenate(([0], idxs[0][:-1]))
>>> outp
array([ 0, -1, -1,  0, -1,  3, -1,  5])

If the first value is a zero however, it will still have value -1, and thus have an index out of range, but it is not clear, at least to me, what should happen in that case.

We can write it a bit more elegant as:

outp = np.full(a.shape, -1)
idxs, = a.nonzero()
if len(idxs) > 0:
    outp[idxs[1:]] = idxs[:-1]
    outp[idxs[0]] = idxs[0]

This allows us to fill in a value before the first non-zero:

outp = np.full(a.shape, -1)
idxs, = a.nonzero()
if len(idxs) > 0:
    outp[idxs[1:]] = idxs[:-1]
    outp[idxs[0]] = idxs[0]
    outp[:idxs[0]] = 0  # or another value before the first non-zero
like image 24
Willem Van Onsem Avatar answered Oct 26 '25 17:10

Willem Van Onsem



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!