This module allows the user to create a more verbose set of ranges. Simple character ranges, and float ranges are supported.
Supported Ranges:
- Basic Integer Ranges
- Float Ranges (as accurate as a float range can get)
- Simple character ranges (lowercase to lowercase, uppercase to uppercase, etc.)
It should work in Python 2 and Python 3.
If you tested this for speed, or want to test this for speed, please post the results! (And your system specs)
Edit: Found a really silly error of mine when using range instead of xrange in these functions!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | #!/usr/bin/env python
# specialrange.py
"""
Contains a general purpose object for arbitrary ranges
i.e.
a-z = abcdefghijklmnopqrstuvwxyz
A-Z = ABCDEFGHIJKLMNOPQRSTUVWXYZ
1-9 = 123456789
0-9 = 0123456789
0-1000 = 0123456789...1000
Copyright 2011 by Sunjay Varma. All Rights Reserved.
Check out www.sunjay.ca
"""
LOWERCASE = "abcdefghijklmnopqrstuvwxyz"
UPPERCASE = LOWERCASE.upper()
LETTERS = LOWERCASE+UPPERCASE
NUMBERS = "123456789"
try:
basestring
xrange
except NameError:
basestring = str
xrange = range
class irange(object):
def __init__(self, start, stop=None, step=1):
if stop is None:
stop = start
start = 0
if not isinstance(start, (float, int)) and not isinstance(stop, (float, int)) and \
type(start) != type(stop):
raise TypeError("The types of start and stop must be the same!")
try:
if "." in start:
start = float(start)
else:
start = int(start)
if "." in stop:
stop = float(stop)
else:
stop = int(stop)
except (ValueError, TypeError):
pass # will be handled later
if isinstance(start, basestring): # the types of start and stop will be the same
assert len(start) and len(stop), "There must be at least one character!"
if len(start) > 1 or len(stop) > 1:
raise ValueError("Longer start and stop values are unsupported!")
if start in LETTERS and stop in LETTERS:
self.iterrange = self._char_range(start, stop, step)
else:
self._cannot_understand(start, stop, step)
elif isinstance(start, (float, int)):
self.iterrange = self._number_range(start, stop, step)
else:
self._cannot_understand(start, stop, step)
def _cannot_understand(self, start, stop, step):
raise ValueError("Cannot understand: %s, %s, or %s"%(start, stop, step))
@staticmethod
def _in_seq(seq, *args):
for x in args:
if x not in seq:
return False
return True
def _char_range(self, start, stop, step):
is_lower = self._in_seq(LOWERCASE, start, stop)
if not(is_lower or self._in_seq(UPPERCASE, start, stop)):
raise ValueError("start and stop must both be in the uppercase or lowercase letters")
seq = is_lower and LOWERCASE or UPPERCASE
start_i = seq.index(start)
stop_i = seq.index(stop)
delta = abs((start_i - stop_i) // step) #+ 1 # the +1 will give even the last character in the result
if stop_i < start_i and step >= 0 or stop_i > start_i and step <= 0:
# the number will never reach
return iter([])
return (seq[start_i + step * i] for i in xrange(delta))
@staticmethod
def _number_range(start, stop, step):
if stop < start and step >= 0 or stop > start and step <= 0:
# the number will never reach
return iter([])
delta = abs((start - stop) // step)
return (start + step * i for i in xrange(int(delta)))
def __iter__(self):
return self
def next(self):
return self.iterrange.next()
def __next__(self): # py3
return self.iterrange.__next__()
srange = lambda start, stop=None, step=1: list(irange(start, stop, step))
|
Usage/Tests:
>>> srange(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> srange(10, step=-1)
[]
>>> srange("a", "z")
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y']
>>> srange("a", "f")
['a', 'b', 'c', 'd', 'e']
>>> srange("A", "Z")
['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y']
>>> srange("q", "a", -1)
['q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b']
>>> srange(-1, 100)
[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 2
1, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 4
1, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 6
1, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 8
1, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> srange(-1, 100, -1)
[]
>>> srange(-10, 10, -1)
[]
>>> srange(-10, 10)
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> srange(100)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 2
2, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 4
2, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 6
2, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 8
2, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> irange(100)
<__main__.irange object at 0x00000000022395F8>
>>> next(irange(100))
0
>>> srange("10", "25")
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
>>> srange("10.5", "25", 1.5)
[10.5, 12.0, 13.5, 15.0, 16.5, 18.0, 19.5, 21.0, 22.5, 24.0]
>>> srange(ord("A"), ord("Z"))
[65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
You said that this should work in python 3 as well as Python 2, and you said "Found a really silly error of mine when using range instead of xrange in these functions!" In Python 3, xrange() doesn't exist, so this won't work in Python 3. You could fix that by adding
and just using range().
Also, you might want to take a look at the string module, in particular string.ascii_letters and string.digits.
This seems cool, although I'm curious if most of the example usage is just supposed to indicate that it works like range() for the cases that range() supports?
Thanks! I didn't catch that! I am aware of the string module, I chose not to use it (for some reason which is lost to me now).
I'm thinking of revising the character range function to use the integer values for each character instead of the string indexing it uses now. The only problem is, if the alphabet (currently set by the locale) is not english, it will not work.
I will edit the code to add in the xrange bug. Thanks! -Sunjay03