Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pythonic way to operate comma-separated list of ranges “1-5,10-25,27-30”

I'm currently working on an api where they send me a str range in this format: "1-5,10-25,27-30" and i need add or remove number conserving the format.

if they send me "1-5,10-25,27-30" and I remove "15" the result must be "1-5,10-14,16-25,27-30" and if they send me "1-5,10-25,27-30" and i add "26" the result must be "1-5,10-30"

i've been trying converting the entire range into a list of numbers, delete the target and converting it again but it's very slow doing in this way becuase they send 8-digits numbers so iter then it's not the best way

how can i do this? is a library for work with this format?

thanks!

like image 361
sarpn Avatar asked Dec 23 '22 16:12

sarpn


2 Answers

intspan deals with ranges of integers and operations on them

>>> from intspan import intspan
>>> s = "1-5,10-25,27-30"
>>> span = intspan(s)
>>> str(span)
'1-5,10-25,27-30'

>>> span.add(26)
>>> str(span)
'1-5,10-30'

>>> span.discard(15)
>>> str(span)
'1-5,10-14,16-30'
like image 129
Chris Charley Avatar answered Dec 25 '22 06:12

Chris Charley


This represents ranges by lists of two-element lists. Two-element lists are used as this allows ranges boundaries to be mutated.

# -*- coding: utf-8 -*-
"""

https://stackoverflow.com/questions/64466231/pythonic-way-to-operate-comma-separated-list-of-ranges-1-5-10-25-27-30

Created on Wed Oct 21 16:29:39 2020

@author: Paddy3118
"""

def to_ranges(txt):
    return [[int(x) for x in r.strip().split('-')]
            for r in txt.strip().split(',')]

def remove_int(rem, ranges):
    for i, r in enumerate(ranges):
        if r[0] <= rem <= r[1]:
            if r[0] == rem:     # range min
                if r[1] > rem:
                    r[0] += 1
                else:
                    del ranges[i]
            elif r[1] == rem:   # range max
                if r[0] < rem:
                    r[1] -= 1
                else:
                    del ranges[i]
            else:               # inside, range extremes.
                r[1], splitrange = rem - 1, [rem + 1, r[1]]
                ranges.insert(i + 1, splitrange)
            break
        if r[0] > rem:  # Not in sorted list
            break
    return ranges
        
def add_int(add, ranges):
    for i, r in enumerate(ranges):
        if r[0] <= add <= r[1]:     # already included
            break
        elif r[0] - 1 == add:      # rough extend to here
            r[0] = add
            break
        elif r[1] + 1 == add:      # rough extend to here
            r[1] = add
            break
        elif r[0] > add:      # rough insert here
            ranges.insert(i, [add, add])
            break
    else:
        ranges.append([add, add])
        return ranges
    return consolidate(ranges)

def consolidate(ranges):
    "Combine overlapping ranges"
    for this, that in zip(ranges, ranges[1:]):
        if this[1] + 1 >= that[0]:  # Ranges interract
            if this[1] >= that[1]:  # this covers that
                this[:], that[:] = [], this
            else:   # that extends this
                this[:], that[:] = [], [this[0], that[1]]
    ranges[:] = [r for r in ranges if r]
    return ranges
                    

sent = "1-5,10-25,27-30"
ll = to_ranges(sent)
assert ll == sorted(ll)

Sample calculations

In [68]: ll
Out[68]: [[1, 5], [10, 25], [27, 30]]

In [69]: add_int(26, ll)
Out[69]: [[1, 5], [10, 30]]

In [70]: add_int(9, ll)
Out[70]: [[1, 5], [9, 30]]

In [71]: add_int(7, ll)
Out[71]: [[1, 5], [7, 7], [9, 30]]

In [72]: remove_int(26, ll)
Out[72]: [[1, 5], [7, 7], [9, 25], [27, 30]]

In [73]: remove_int(9, ll)
Out[73]: [[1, 5], [7, 7], [10, 25], [27, 30]]

In [74]: remove_int(7, ll)
Out[74]: [[1, 5], [10, 25], [27, 30]]

In [75]: 
like image 21
Paddy3118 Avatar answered Dec 25 '22 05:12

Paddy3118