Find Start/Stop Indices Where Array Is True If I have a list that contains True and False values, how can I find the start and stop indices of where there are blocks of True values?

x = [True, False, False, True, True, True, False, True, False, False, False, True, True, False, True]

The output that I am looking for is:

[[0,  0],
 [3,  5],
 [7,  7],
 [11, 12],
 [14, 14]
]


Here, the value in the first column is the start index and the value in the second column is the stop index.

One solution that I have is:

start = []
stop = []
if y[0]:
    start.append(0)
for i in range(len(y)):
    if y[i] and not y[i-1]:
        start.append(i)
    if i &gt; 0 and (not y[i] and y[i-1]):
        stop.append(i-1)
if y[len(y)-1]:
    stop.append(len(y)-1)

print(list(map(list, zip(start, stop))))
Enumerate

for i in range(len(y)): is an antipattern.  If you want indices (i) and the values at those indices (y[i]) the Pythonic way is using enumerate:

for i, yi in enumerate(y):


Start indices

Let's take a moment and split this task into two parts: the start indices and the stop indices.  First, the start indices:

start = []
if y[0]:
    start.append(0)
for i in range(len(y)):
    if y[i] and not y[i-1]:
        start.append(i)


That almost looks like it could be replaced by list comprehension ...

start = [i for i, yi in enumerate(y) if yi and not y[i-1]]


... except for that y[i-1] part, which may wrap around and grab a value from the end of the array.  What you really want to do is zip the y[i] and y[i-1] sequences together.  We just need to start the y[i-1] sequence off with a leading False value:

start = [i for i, (y1, y2) in enumerate(zip([False] + y[:-1], y)) if y2 &gt; y1]


Stop indices

The stop indices are similar, zipping y[i] with y[i+1] with a trailing False:

stop = [i for i, (y1, y2) in enumerate(zip(y, y[1:] + [False])) if y2 &lt; y1]


Groupby

And now for something completely different.

itertools.groupby() takes sequential items with matching keys, groups them together, and emits them together with their key.  We just need the True and False values to be the key, and attach the index numbers to the True/False values.

&gt;&gt;&gt; [list(group) for key, group in groupby(enumerate(x), key=lambda ix: ix[1])]
[[(0, True)],
 [(1, False), (2, False)],
 [(3, True), (4, True), (5, True)],
 [(6, False)],
 [(7, True)],
 [(8, False), (9, False), (10, False)],
 [(11, True), (12, True)],
 [(13, False)],
 [(14, True)]]


(output reformatted to show one inner list per line, for clarity)

But we only want the groups where the key is True:

&gt;&gt;&gt; [list(group)
...  for key, group in groupby(enumerate(x), key=lambda ix: ix[1])
...  if key]
[[(0, True)],
 [(3, True), (4, True), (5, True)],
 [(7, True)],
 [(11, True), (12, True)],
 [(14, True)]]


For each group, we want the first and the last items in the group:

&gt;&gt;&gt; [[group[0], group[-1]]
...  for group in (list(group)
...                for key, group in groupby(enumerate(x),
...                                          key=lambda ix: ix[1])
...                if key)]
[[(0, True), (0, True)],
 [(3, True), (5, True)],
 [(7, True), (7, True)],
 [(11, True), (12, True)],
 [(14, True), (14, True)]]


Actually, we only want the indices stored in those items:

start_stop = [[group[0][0], group[-1][0]]
              for group in (list(group)
                            for key, group in groupby(enumerate(x),
                                                      key=lambda ix: ix[1])
                            if key)]


Which gives:

&gt;&gt;&gt; start_stop
[[0, 0], [3, 5], [7, 7], [11, 12], [14, 14]]




In the comments, Graipher suggests:


  Personally, I would make getting the groups a generator expression assigned to a variable and then iterator over that in a list comprehension ... 


from itertools import groupby
from operator import itemgetter

groups = (list(group) for key, group in groupby(enumerate(x), key=itemgetter(1))
                      if key)
start_stop = [[group[0][0], group[-1][0]] for group in groups]



  ... using operator.itemgetter (instead of the lambda ix: ix[1]) for some more readability.




In the comments, RootTwo suggests:


  This could be simplified by grouping the indices directly instead of using enumerate:


[(group[0], group[-1]) for group in (
 list(group) for key, group in groupby(range(len(x)), key=x.__getitem__) if key)]




Timings, for a list of 15,000,000 entries (the original x list, replicated one million times):

Original:   7.3 seconds
AJNeufeld:  6.4 seconds
Graipher:   5.5 seconds
RootTwo:    3.1 seconds