bontchev
bontchev

Reputation: 568

Parsing an optional list of ranges

My script needs to take an option followed by a list of pages, specified as comma-separated list of ranges, and to process the expanded list of pages. So, for instance,

script.py -a 2,4-6,9,10-13

should get the list

[2, 4, 5, 6, 9, 10, 11, 12, 13]

to work with. Currently I am doing this:

import argparse

def getList(argument):
    ranges = list(argument.split(","))
    newRange = []
    for theRange in ranges:
        subRange = list(map(int, theRange.split("-")))
        if (len(subRange) > 1):
            newRange.extend(range(subRange[0], subRange[1] + 1))
        else:
            newRange.extend(subRange)
    newRange.sort()
    return newRange

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--pages", type=getList, dest="pages", default=[], help="pages to process")
    args = parser.parse_args()

and it works but I'm relatively new to this Python thing and was wondering if there is a better way to do it?

Edit: I fail to see why it was marked as a duplicate question. None of the answers to the questions it was marked as a duplicate of does exactly what I have described above. The idea is not to process a simple list of space- or comma-delimited arguments but something more complex - a list of ranges that have to be expanded.

Upvotes: 2

Views: 349

Answers (1)

larsks
larsks

Reputation: 311625

Here's one solution. It's not necessarily the best. In particular, there is probably a more performant version involving chained iterators rather than nested iterators.

Given:

>>> range='2,4-6,9,10-13'

You could do something like:

>>> result = [range(int(y[0]), int(y[1])+1) 
... for y in [(x.split('-') + [x])[:2]
... for x in r.split(',')]]

Which gets you:

>>> result
[[2], [4, 5, 6], [9], [10, 11, 12, 13]]

If we unpack those comprehensions, the innermost one iterates over:

>>> range.split(',')
['2', '4-6', '9', '10-13']

The second is:

>>> result = range.split(',')
>>> [(x.split('-') + [x])[:2] for x in result]
[['2', '2'], ['4', '6'], ['9', '9'], ['10', '13']]

The expression (x.split('-') + [x])[:2] is ensuring that we have a two-item list, even for single numbers, so that we can pass that to the range function.

Finally, we iterate over that result:

>>> [range(int(y[0]), int(y[1])+1) for y in <the result from the rpevious operation]

Which is hopefuly obvious. Given the list [2, 2], we call range(2, 3) which gives us [2]. Applied across all the results from the previous operation, this gets us the result we saw earlier:

[[2], [4, 5, 6], [9], [10, 11, 12, 13]]

And now your problem is "how do I flatten a list", to which one solution (from here) is:

>>> import itertools
>>> list(itertools.chain.from_iterable(result))
[2, 4, 5, 6, 9, 10, 11, 12, 13]

Upvotes: 1

Related Questions