Collapse sequences of numbers into ranges

Doable. Let's see if this can be done with pandas.

import pandas as pd

data = ['10215', '10216', '10277', ...]
# Load data as series.
s = pd.Series(data)
# Find all consecutive rows with a difference of one 
# and bin them into groups using `cumsum`. 
v = s.astype(int).diff().bfill().ne(1).cumsum() 
# Use `groupby` and `apply` to condense the consecutive numbers into ranges.
# This is only done if the group size is >1.
ranges = (
    s.groupby(v).apply(
        lambda x: '-'.join(x.values[[0, -1]]) if len(x) > 1 else x.item()).tolist())

print (ranges)
['10215-10216',
 '10277-10282',
 '10292-10293',
 '10295-10326',
 '10344',
 '10399-10406',
 '10415-10418',
 '10430',
 '10448',
 '10492-10495',
 '10574-10659',
 '10707-10710',
 '10792-10795',
 '10908',
 '10936-10939',
 '11108-11155',
 '11194-11235',
 '10101-10102',
 '10800',
 '11236']

Your data must be sorted for this to work.


You can just use a simple loop here with the following logic:

  1. Create a list to store the ranges (ranges).
  2. Iterate over the values in your list (l)
  3. If ranges is empty, append a list with the first value in l to ranges
  4. Otherwise if the difference between the current and previous value is 1, append the current value to the last list in ranges
  5. Otherwise append a list with the current value to ranges

Code:

l = ['10215', '10216', '10277', '10278', '10279', '10280', ...]

ranges = []
for x in l:
    if not ranges:
        ranges.append([x])
    elif int(x)-prev_x == 1:
        ranges[-1].append(x)
    else:
        ranges.append([x])
    prev_x = int(x)

Now you can compute your final ranges by concatenating the first and last element of each list in ranges (if there are at least 2 elements).

final_ranges = ["-".join([r[0], r[-1]] if len(r) > 1 else r) for r in ranges]
print(final_ranges)
#['10215-10216',
# '10277-10282',
# '10292-10293',
# '10295-10326',
# '10344',
# '10399-10406',
# '10415-10418',
# '10430',
# '10448',
# '10492-10495',
# '10574-10659',
# '10707-10710',
# '10792-10795',
# '10908',
# '10936-10939',
# '11108-11155',
# '11194-11235',
# '10101-10102',
# '10800',
# '11236']

This also assumes your data is sorted. You could simplify the code to combine items 3 and 5.


For purely educational purposes (this is much more inefficient that the loop above), here's the same thing using map and reduce:

from functools import reduce

def myreducer(ranges, x):
    if not ranges:
        return [[x]]
    elif (int(x) - int(ranges[-1][-1]) == 1):
        return ranges[:-1] + [ranges[-1]+[x]] 
    else:
        return ranges + [[x]]

final_ranges = map(
    lambda r: "-".join([r[0], r[-1]] if len(r) > 1 else r),
    reduce(myreducer, l, [])
)