6
\$\begingroup\$

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)])
\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

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']
\$\endgroup\$
1
\$\begingroup\$

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)]))
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.