Question

In a Pylons webapp, I need to take a string such as "<3, 45, 46, 48-51, 77" and create a list of ints (which are actually IDs of objects) to search on.

Any suggestions on ways to do this? I'm new to Python, and I haven't found anything out there that helps with this kind of thing.

The list would be: [1, 2, 3, 45, 46, 48, 49, 50, 51, 77]

Was it helpful?

Solution

Use parseIntSet from here

I also like the pyparsing implementation in the comments at the end.

The parseIntSet has been modified here to handle "<3"-type entries and to only spit out the invalid strings if there are any.

#! /usr/local/bin/python
import sys
import os

# return a set of selected values when a string in the form:
# 1-4,6
# would return:
# 1,2,3,4,6
# as expected...

def parseIntSet(nputstr=""):
    selection = set()
    invalid = set()
    # tokens are comma seperated values
    tokens = [x.strip() for x in nputstr.split(',')]
    for i in tokens:
        if len(i) > 0:
            if i[:1] == "<":
                i = "1-%s"%(i[1:])
        try:
            # typically tokens are plain old integers
            selection.add(int(i))
        except:
            # if not, then it might be a range
            try:
                token = [int(k.strip()) for k in i.split('-')]
                if len(token) > 1:
                    token.sort()
                    # we have items seperated by a dash
                    # try to build a valid range
                    first = token[0]
                    last = token[len(token)-1]
                    for x in range(first, last+1):
                        selection.add(x)
            except:
                # not an int and not a range...
                invalid.add(i)
    # Report invalid tokens before returning valid selection
    if len(invalid) > 0:
        print "Invalid set: " + str(invalid)
    return selection
# end parseIntSet

print 'Generate a list of selected items!'
nputstr = raw_input('Enter a list of items: ')

selection = parseIntSet(nputstr)
print 'Your selection is: '
print str(selection)

And here's the output from the sample run:

$ python qq.py
Generate a list of selected items!
Enter a list of items: <3, 45, 46, 48-51, 77
Your selection is:
set([1, 2, 3, 45, 46, 77, 48, 49, 50, 51])

OTHER TIPS

I've created a version of @vartec's solution which I feel is more readable:

def _parse_range(numbers: str):
    for x in numbers.split(','):
        x = x.strip()
        if x.isdigit():
            yield int(x)
        elif x[0] == '<':
            yield from range(0, int(x[1:]))
        elif '-' in x:
            xr = x.split('-')
            yield from range(int(xr[0].strip()), int(xr[1].strip())+1)
        else:
            raise ValueError(f"Unknown range specified: {x}")

In the process, the function became a generator :)

rng = "<3, 45, 46, 48-51, 77"
ids = []
for x in map(str.strip,rng.split(',')):
    if x.isdigit():
        ids.append(int(x))
        continue
    if x[0] == '<':
        ids.extend(range(1,int(x[1:])+1))
        continue
    if '-' in x:
        xr = map(str.strip,x.split('-'))
        ids.extend(range(int(xr[0]),int(xr[1])+1))
        continue
    else:
        raise Exception, 'unknown range type: "%s"'%x

First, you'll need to figure out what kind of syntax you'll accept. You current have three in your example:

  1. Single number: 45, 46

  2. Less than operator

  3. Dash ranging: 48-51

After that, it's just a matter of splitting the string into tokens, and checking the format of the token.

>>> print range.__doc__
range([start,] stop[, step]) -> list of integers

Return a list containing an arithmetic progression of integers. range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0. When step is given, it specifies the increment (or decrement). For example, range(4) returns [0, 1, 2, 3]. The end point is omitted! These are exactly the valid indices for a list of 4 elements.

>>> range(33,44)
[33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43]
>>> range(1,3)
[1, 2]

I imagine you could iterate your list, and call range appropriately.

>>> def lessThan(n) :
...  return range(n+1)
...
>>> lessThan(4)
[0, 1, 2, 3, 4]
>>> def toFrom(n,m):
...  return range(n,m)
...
>>> toFrom(33,44)
[33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43]

Then split the string on commas, and for each bit, parse it enough to figure out what function to call, catenating the lists returned.

Anything more and I'd have written it for you.

I also had to do something similar for an app lately.

If you don't need concrete numbers but just a way to see whether a given number is in the range, you might consider parsing it to a Python expression you can eval into a lambda. For example <3, 5-10, 12 could be func=(lambda x:x<3 or (5 <= x <= 10) or x==12)). Then you can just call the lambda, func(11) to see if 11 belongs in there.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top