def f(x):
result = []
for part in x.split(','):
if '-' in part:
a, b = part.split('-')
a, b = int(a), int(b)
result.extend(range(a, b + 1))
else:
a = int(part)
result.append(a)
return result
>>> f('1,2,5-7,10')
[1, 2, 5, 6, 7, 10]
Answer from FogleBird on Stack Overflowdef f(x):
result = []
for part in x.split(','):
if '-' in part:
a, b = part.split('-')
a, b = int(a), int(b)
result.extend(range(a, b + 1))
else:
a = int(part)
result.append(a)
return result
>>> f('1,2,5-7,10')
[1, 2, 5, 6, 7, 10]
I was able to make a true comprehension on that question:
>>> def f(s):
return sum(((list(range(*[int(j) + k for k,j in enumerate(i.split('-'))]))
if '-' in i else [int(i)]) for i in s.split(',')), [])
>>> f('1,2,5-7,10')
[1, 2, 5, 6, 7, 10]
>>> f('1,3-7,10,11-15')
[1, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15]
the other answer that pretended to have a comprehension was just a for loop because the final list was discarded. :)
For python 2 you can even remove the call to list!
Disclaimer: I'm not a Python programmer!
Your code isn't that bad. It's readable enough for me to understand it. B+ for readability!
Currently, you have this function:
def giveRange(numString:str):
z=numString.split("-")
if(len(z)==1):
return [int(z[0])]
elif(len(z)==2):
return list(range(int(z[0]),int(z[1])+1))
else:
raise IndexError("TOO MANY VALS!")
Why don't you simply store the length in a variable?
Like this:
length=len(z)
if(length==1):
return [int(z[0])]
elif(length==2):
return list(range(int(z[0]),int(z[1])+1))
else:
raise IndexError("TOO MANY VALS!")
Now, you don't have to calculate the length twice, only once.
The name z is a really bad name. Better names would be numbers, pieces or something similar.
Looking at the definition of chain(), it seems to accept any iterable, which a range() happens to be. So, you probably don't need that list(), leaving this:
return range(int(z[0]),int(z[1])+1)
On your function unpackNums instead of creating an empty set(), you could use the a set comprehension:
def unpackNums(numString:str):
return {x for x in set(chain(*map(giveRange,numString.split(","))))}
If you notice any inaccuracies, please comment.
Since the whole exercise is a string-transformation problem, I suggest performing it using a regex substitution.
import re
def expand_ranges(s):
return re.sub(
r'(\d+)-(\d+)',
lambda match: ','.join(
str(i) for i in range(
int(match.group(1)),
int(match.group(2)) + 1
)
),
s
)
I think that expand_ranges would be a more descriptive name than unpackNums.
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])
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 :)
» pip install py-range-parse
» pip install DateRangeParser
» pip install RangeParser
For parsing HTML in Python, I prefer BeautifulSoup. This is assuming you want to find links, and not just the literal <a href=, which you can easily do searching through the string.
This is a job for Beautiful Soup for sure:
>>> from BeautifulSoup import BeautifulSoup
>>> import urllib2
>>> page = urllib2.urlopen('http://stackoverflow.com/')
>>> soup = BeautifulSoup(page)
>>> links = soup.html.body.findAll('a', limit=10)
>>> for i, link in enumerate(links):
... print i, ':', link.text, ' -- ', link['href']
...
0 : Stack Exchange -- http://stackexchange.com
1 : log in -- /users/login
2 : blog -- http://blog.stackoverflow.com
3 : careers -- http://careers.stackoverflow.com
4 : chat -- http://chat.stackoverflow.com
5 : meta -- http://meta.stackoverflow.com
6 : about -- /about
7 : faq -- /faq
8 : Stack Overflow -- /
9 : Questions -- /questions
There are a lot of links on that front-page; I've limited the output to the first ten!