Welcome, guest | Sign In | My Account | Store | Cart

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!

Python, 109 lines
  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]

2 comments

Michael Seydel 13 years ago  # | flag

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

try:
    xrange = range
except NameError:
    pass # this happens in Python 3`

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?

Sunjay Varma (author) 13 years ago  # | flag

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

Created by Sunjay Varma on Sun, 20 Feb 2011 (MIT)
Python recipes (4591)
Sunjay Varma's recipes (12)

Required Modules

  • (none specified)

Other Information and Tasks