16
votes

I have the following array

  a = [1, 2, 3, 0, 0, 0, 0, 0, 0, 4, 5, 6, 0, 0, 0, 0, 9, 8, 7,0,10,11]

What I would like to find the start and the end index of the array where the values are zeros consecutively, for the array above the output looks as follows

  [3,8],[12,15],[19]

I want to do it as efficient as possible.

3
Why down vote, Please write the resaon - Shan
It wasn't me but it's traditional to say something like "I have this code" (and show it) and it doesn't quite work. You haven't shown what you've tried. - doctorlove
well, i can just go through the array with a loop and mark the start and end indices, I thought people who know python would understand it easily - Shan
If pure Python is an option then you can use itertools.groupby for this. - Ashwini Chaudhary
For the record, that syntax doesn't construct an array but a list. - DSM

3 Answers

36
votes

Here's a fairly compact vectorized implementation. I've changed the requirements a bit, so the return value is a bit more "numpythonic": it creates an array with shape (m, 2), where m is the number of "runs" of zeros. The first column is the index of the first 0 in each run, and the second is the index of the first nonzero element after the run. (This indexing pattern matches, for example, how slicing works and how the range function works.)

import numpy as np

def zero_runs(a):
    # Create an array that is 1 where a is 0, and pad each end with an extra 0.
    iszero = np.concatenate(([0], np.equal(a, 0).view(np.int8), [0]))
    absdiff = np.abs(np.diff(iszero))
    # Runs start and end where absdiff is 1.
    ranges = np.where(absdiff == 1)[0].reshape(-1, 2)
    return ranges

For example:

In [236]: a = [1, 2, 3, 0, 0, 0, 0, 0, 0, 4, 5, 6, 0, 0, 0, 0, 9, 8, 7, 0, 10, 11]

In [237]: runs = zero_runs(a)

In [238]: runs
Out[238]: 
array([[ 3,  9],
       [12, 16],
       [19, 20]])

With this format, it is simple to get the number of zeros in each run:

In [239]: runs[:,1] - runs[:,0]
Out[239]: array([6, 4, 1])

It's always a good idea to check the edge cases:

In [240]: zero_runs([0,1,2])
Out[240]: array([[0, 1]])

In [241]: zero_runs([1,2,0])
Out[241]: array([[2, 3]])

In [242]: zero_runs([1,2,3])
Out[242]: array([], shape=(0, 2), dtype=int64)

In [243]: zero_runs([0,0,0])
Out[243]: array([[0, 3]])
1
votes

You can use itertools to achieve your expected result.

from itertools import groupby
a= [1, 2, 3, 0, 0, 0, 0, 0, 0, 4, 5, 6, 0, 0, 0, 0, 9, 8, 7,0,10,11]
b = range(len(a))
for group in groupby(iter(b), lambda x: a[x]):
    if group[0]==0:
        lis=list(group[1])
        print [min(lis),max(lis)]
-1
votes

Here is a custom function, not sure the most efficient but works :

def getZeroIndexes(li):
  begin = 0
  end = 0
  indexes = []
  zero = False
  for ind,elt in enumerate(li):
    if not elt and not zero:
      begin = ind
      zero = True
    if not elt and zero:
      end = ind
    if elt and zero:
      zero = False
      if begin == end:
        indexes.append(begin)
      else:
        indexes.append((begin, end))

  return indexes