26

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]

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 would be as follows

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

I want to achieve this as efficiently as possible.

5
  • Why down vote, Please write the resaon Commented Jul 22, 2014 at 10:40
  • 2
    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. Commented Jul 22, 2014 at 10:41
  • 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 Commented Jul 22, 2014 at 10:48
  • 1
    If pure Python is an option then you can use itertools.groupby for this. Commented Jul 22, 2014 at 11:17
  • 2
    For the record, that syntax doesn't construct an array but a list. Commented Jul 22, 2014 at 11:58

3 Answers 3

55

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]])
Sign up to request clarification or add additional context in comments.

5 Comments

any way to do that with pandas ?
Nice solution! Is there a reason for using np.int8 data type in iszero? I think if we simply use booleans we can also avoid np.abs() and simply set absdiff = np.diff(iszero). Am I missing something?
This is a clever solution. I modified it to count the number of ties in the Mann-Kendall trend test stackoverflow.com/a/68442829/2005415
Oneliner: np.ediff1d(np.r_[0, a == 0, 0]).nonzero()[0].reshape(-1, 2)
More readable with diff since numpy 1.16: np.diff(booleanArray, prepend=False, append=False).nonzero()[0].reshape(-1, 2).
1

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 Comment

This will return [19, 19], I think OP expects just [19]. And instead of creating an unnecessary list b, try to use enumerate(a).
-1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.