# Find all subranges of consecutive values in an array

I was looking at a dataset with a year column. I wanted all the years with matching a particular event. I then get a list of years which is not easy to read:

2010, 2011, 2012, 2013, 2015, 2017, 2018, 2019, 2020


It would be much better to display them as:

2010-2013, 2015, 2017-2020


I was looking for a builtin function in Python, but eventually, I wrote this:

import numpy as np

def ranges(array):
start = None
for i, k in zip(np.diff(array, append=[array[-1]]), array):
if i == 1:
if start is None:
start = k
continue
yield(k if start is None else (start, k))
start = None


Is there a more pythonic way that does the same with less?

At the end I could do this:

years = [2010, 2011, 2012, 2013, 2015, 2017, 2018, 2019, 2020]
', '.join([f'{x[0]}-{x[1]}' if isinstance(x, tuple) else str(x)
for x in ranges(years)])


I'm glad you asked this question, as I've had a difficult time finding the solution recently as well. However, I have now found that the solution is to use the more_itertools consecutive_groups function:

from more_itertools import consecutive_groups
x = [2010, 2011, 2012, 2013, 2015, 2017, 2018, 2019, 2020]

# create an intermediate list to avoid having unnecessary list calls in the next line
subsets = [list(group) for group in consecutive_groups(x)]

result = [f"{years[0]}-{years[-1]}" if len(years) > 1 else f"{years[0]}" for years in subsets]
# ['2010-2013', '2015', '2017-2020']


I like the more_itertools answer by @kraigolas as it is super clean. However, I don't know this module at all (looks like maybe I should look into it though). So if it were me, and since you already have a solution using numpy I might do:

def get_intervals(data):
intervals = numpy.split(data, numpy.where(numpy.diff(data) > 1)[0] + 1)
return [(interval[0], interval[-1]) for interval in intervals]


To print I would then use a format method like:

def fmt_for_print(interval):
return f"{ interval[0] }" if interval[0] == interval[1] else f"{ interval[0] }-{ interval[1] }"


Then you can:

years = [2010, 2011, 2012, 2013, 2015, 2017, 2018, 2019, 2020]
print(', '.join([fmt_for_print(interval) for interval in get_intervals(years)]))