PEP 276 Simple Iterator for ints (fwd)

James_Althoff at i2.com James_Althoff at i2.com
Tue Dec 11 21:09:08 EST 2001


Span: The Next Generation ...   :-)

Borrowing from Nick Mathewson and myself, here is another implementation of
intervals that uses relational operators instead of the highly popular /
and // of "span" fame. <wink>

This time the class is called "ints" (as in Nick's example).  Because
relational operators are automatically "and-ed" together when cascaded,
instances of class ints require side effects to maintain state.  This means
that a single instance ("span", for example) won't work.  Instead, it is
necessary to do ints() always.

Another limitation (compared to "span") is that one-sided relational
shortcuts are only partially supported.  Because the magic methods for
relational operators don't have leftside and rightside versions it isn't
possible (unless I'm missing something) to distinguish (in the magic
methods) between "ints() < 5" and "5 > ints()", for example.  So to keep
things simple within the bounds of this limitation, the current
implementation returns (for one-sided shortcuts) [] (an empty list) except
for the common cases of "ints() < n" and "ints() <= n" where n is positive.
In both cases, 0 (inclusive) is the start of the interval.  (Actually, "n >
ints()" and "n >= ints()" do the same thing because of the limitation noted
above).

"Steps" are implemented using a (positive) "step" constructor argument as
in "ints(step=2)", for example.

This example implementation supports non-integer bounds as requested by
interested posters (note to David :-).

Also, this implementation doesn't use xrange and so you can do a closed
interval that includes sys.maxint (by popular demand, not ;-).

Examples and code are included below.

(To take the next step and go from "-5 <= ints() <= 5" to "-5 <= ... <= 5"
we would need syntax changes that make "..." an alternate spelling for
"ints()" when used in a relational expression.  I have no idea if such a
thing is feasible.)

Happy interval-ing,

Jim

===================================================

C:\>python
Python 2.2b1 (#25, Oct 19 2001, 11:44:52) [MSC 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>

... after imports, etc.

>>>
>>> for i in -5 <= ints() <= 5:
...     print i,
...
-5 -4 -3 -2 -1 0 1 2 3 4 5
>>>
>>> for i in -5 < ints() < 5:
...     print i,
...
-4 -3 -2 -1 0 1 2 3 4
>>>
>>> for i in 5 >= ints() >= -5:
...     print i,
...
5 4 3 2 1 0 -1 -2 -3 -4 -5
>>>
>>> for i in ints() < 5:
...     print i,
...
0 1 2 3 4
>>>
>>> for i in -5 <= ints(step=2) <= 5:
...     print i,
...
-5 -3 -1 1 3 5
>>>
>>> for i in -4.5 < ints() < 4.5:
...     print i,
...
-4 -3 -2 -1 0 1 2 3 4
>>>
>>> for i in 4.0 >= ints(step=2) >= -4.0:
...     print i,
...
4 2 0 -2 -4
>>>
>>> for i in ints() < 4.5:
...     print i,
...
0 1 2 3 4
>>>
>>> import sys
>>>
>>> for i in sys.maxint - 2 <= ints() <= sys.maxint:
...     print i,
...
2147483645 2147483646 2147483647
>>>

... test examples

>>> runtests()

passed: 1  -5 <= ints() <=  5    [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
passed: 1  -5 <= ints() <   5    [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]
passed: 1  -5 <  ints() <   5    [-4, -3, -2, -1, 0, 1, 2, 3, 4]
passed: 1  -5 <  ints() <=  5    [-4, -3, -2, -1, 0, 1, 2, 3, 4, 5]
passed: 1   5 >= ints() >= -5    [5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -5]
passed: 1   5 >= ints() >  -5    [5, 4, 3, 2, 1, 0, -1, -2, -3, -4]
passed: 1   5 >  ints() >  -5    [4, 3, 2, 1, 0, -1, -2, -3, -4]
passed: 1   5 >  ints() >= -5    [4, 3, 2, 1, 0, -1, -2, -3, -4, -5]
passed: 1  -5 <= ints(step=2) <=  5    [-5, -3, -1, 1, 3, 5]
passed: 1  -5 <= ints(step=2) <   5    [-5, -3, -1, 1, 3]
passed: 1  -5 <  ints(step=2) <   5    [-4, -2, 0, 2, 4]
passed: 1  -5 <  ints(step=2) <=  5    [-4, -2, 0, 2, 4]
passed: 1   5 >= ints(step=2) >= -5    [5, 3, 1, -1, -3, -5]
passed: 1   5 >= ints(step=2) >  -5    [5, 3, 1, -1, -3]
passed: 1   5 >  ints(step=2) >  -5    [4, 2, 0, -2, -4]
passed: 1   5 >  ints(step=2) >= -5    [4, 2, 0, -2, -4]
passed: 1  ints() <= 5    [0, 1, 2, 3, 4, 5]
passed: 1  ints() <  5    [0, 1, 2, 3, 4]
passed: 1  5 >= ints()    [0, 1, 2, 3, 4, 5]
passed: 1  5 >  ints()    [0, 1, 2, 3, 4]
passed: 1  ints() >= 5    []
passed: 1  ints() >  5    []
passed: 1  5 <= ints()    []
passed: 1  5 <  ints()    []
passed: 1  ints() <= -5    []
passed: 1  ints() <  -5    []
passed: 1  ints() >= -5    []
passed: 1  ints() >  -5    []
passed: 1  -5 <= ints()    []
passed: 1  -5 <  ints()    []
passed: 1  -5 >= ints()    []
passed: 1  -5 >  ints()    []
passed: 1  sys.maxint-1 < ints() <= sys.maxint    [2147483647]
passed: 1  1.5 <= ints() <= 4.5    [2, 3, 4]
passed: 1  1.5 <= ints() <  4.5    [2, 3, 4]
passed: 1  1.5 <  ints() <  4.5    [2, 3, 4]
passed: 1  1.5 <  ints() <= 4.5    [2, 3, 4]
passed: 1  1.0 <= ints() <= 4.0    [1, 2, 3, 4]
passed: 1  1.0 <= ints() <  4.0    [1, 2, 3]
passed: 1  1.0 <  ints() <  4.0    [2, 3]
passed: 1  1.0 <  ints() <= 4.0    [2, 3, 4]
passed: 1  -4.5 <= ints() <= -1.5    [-4, -3, -2]
passed: 1  -4.5 <= ints() <  -1.5    [-4, -3, -2]
passed: 1  -4.5 <  ints() <  -1.5    [-4, -3, -2]
passed: 1  -4.5 <  ints() <= -1.5    [-4, -3, -2]
passed: 1  -4.0 <= ints() <= -1.0    [-4, -3, -2, -1]
passed: 1  -4.0 <= ints() <  -1.0    [-4, -3, -2]
passed: 1  -4.0 <  ints() <  -1.0    [-3, -2]
passed: 1  -4.0 <  ints() <= -1.0    [-3, -2, -1]
passed: 1  ints() <= 4.5    [0, 1, 2, 3, 4]
passed: 1  ints() <  4.5    [0, 1, 2, 3, 4]
passed: 1  ints() <= 4.0    [0, 1, 2, 3, 4]
passed: 1  ints() <  4.0    [0, 1, 2, 3]
passed: 1  ints() > 4.5    []
passed: 1  4.5 < ints()    []


=================================================

# Example implementation for Python 2.2:


from __future__ import generators
from math import floor,ceil


class ints:

    import operator

    testDict = {  # i is in interval if this test is true
        (1,0):operator.__lt__,  # up == 1, upperClosed == 0
        (1,1):operator.__le__,  # up == 1, upperClosed == 1
        (0,0):operator.__gt__,  # up == 0, lowerClosed == 0
        (0,1):operator.__ge__}  # up == 0, lowerClosed == 1

    def __init__(self,step=None):
        if step is not None and step <= 0:
            raise ValueError
        self.step = step  # None or integer > 0
        self.lower = None  # lower bound of interval
        self.lowerClosed = None  # 1: yes, 0: no
        self.upper = None  # upper bound of interval
        self.upperClosed = None  # 1: yes, 0: no
        self.up = None  # 1: increasing interval, 0: decreasing interval

    def __iter__(self):
        '''return an iterator that generates integers in the interval.'''
        if self.upper is None:
            return  # interval is [] if no upper bound specified
        start,stop,delta,test = self.getBounds()  # delta is positive or
negative
        i = start
        while 1:
            if not test(i,stop):
                break
            yield i
            i += delta

    def getBounds(self):
        lower = self.lower
        upper = self.upper
        lowerClosed = self.lowerClosed
        upperClosed = self.upperClosed
        up = self.up
        delta = self.step
        # fill in missing data
        if delta is None:
            delta = 1
        if lower is None:
            lower = 0
            up = 1
        if lowerClosed is None:
            lowerClosed = 1
        if up is None:
            up = 1
        if up:
            start = lower
            floorStart = floor(start)
            if not (floorStart == start and lowerClosed):
                start = floorStart + 1
            stop = upper
            test = self.testDict[up,upperClosed]
        else:
            start = upper
            ceilStart = ceil(start)
            if not (ceilStart == start and upperClosed):
                start = ceilStart - 1
            stop = lower
            delta = -delta
            test = self.testDict[up,lowerClosed]
        start = int(start)
        return start,stop,delta,test

    def __lt__(self,n):
        self.upper = n
        self.upperClosed = 0
        if self.up is None:
            self.up = 0  # on first encounter, assume decreasing
        return self

    def __le__(self,n):
        self.upper = n
        self.upperClosed = 1
        if self.up is None:
            self.up = 0  # on first encounter, assume decreasing
        return self

    def __gt__(self,n):
        self.lower = n
        self.lowerClosed = 0
        if self.up is None:
            self.up = 1  # on first encounter, assume increasing
        return self

    def __ge__(self,n):
        self.lower = n
        self.lowerClosed = 1
        if self.up is None:
            self.up = 1  # on first encounter, assume increasing
        return self

    def __nonzero__(self):
         return 1  # force evaluation of both sides of two-way relationals


#$ Testing

def test(testItem):
    import sys
    interval = eval(testItem[0],globals(),{'sys':sys})
    result = list(interval) == testItem[1]
    print 'passed:', result, '', testItem[0], '  ', list(interval)
    if not result:
        print interval
        print list(interval)
        print testItem[1]

def runtests():
    import sys
    testList = [
        ('-5 <= ints() <=  5', range(-5,6)),
        ('-5 <= ints() <   5', range(-5,5)),
        ('-5 <  ints() <   5', range(-4,5)),
        ('-5 <  ints() <=  5', range(-4,6)),
        (' 5 >= ints() >= -5', range(5,-6,-1)),
        (' 5 >= ints() >  -5', range(5,-5,-1)),
        (' 5 >  ints() >  -5', range(4,-5,-1)),
        (' 5 >  ints() >= -5', range(4,-6,-1)),
        ('-5 <= ints(step=2) <=  5', range(-5,6,2)),
        ('-5 <= ints(step=2) <   5', range(-5,5,2)),
        ('-5 <  ints(step=2) <   5', range(-4,5,2)),
        ('-5 <  ints(step=2) <=  5', range(-4,6,2)),
        (' 5 >= ints(step=2) >= -5', range(5,-6,-2)),
        (' 5 >= ints(step=2) >  -5', range(5,-5,-2)),
        (' 5 >  ints(step=2) >  -5', range(4,-5,-2)),
        (' 5 >  ints(step=2) >= -5', range(4,-6,-2)),
        ('ints() <= 5',  range(0,6)),
        ('ints() <  5',  range(0,5)),
        ('5 >= ints()',  range(0,6)),
        ('5 >  ints()',  range(0,5)),
        ('ints() >= 5', []),
        ('ints() >  5', []),
        ('5 <= ints()',  []),
        ('5 <  ints()',  []),
        ('ints() <= -5', []),
        ('ints() <  -5', []),
        ('ints() >= -5', []),
        ('ints() >  -5', []),
        ('-5 <= ints()', []),
        ('-5 <  ints()', []),
        ('-5 >= ints()', []),
        ('-5 >  ints()', []),
        ('sys.maxint-1 < ints() <= sys.maxint', [sys.maxint]),
        ('1.5 <= ints() <= 4.5', [2,3,4]),
        ('1.5 <= ints() <  4.5', [2,3,4]),
        ('1.5 <  ints() <  4.5', [2,3,4]),
        ('1.5 <  ints() <= 4.5', [2,3,4]),
        ('1.0 <= ints() <= 4.0', [1,2,3,4]),
        ('1.0 <= ints() <  4.0', [1,2,3]),
        ('1.0 <  ints() <  4.0', [2,3]),
        ('1.0 <  ints() <= 4.0', [2,3,4]),
        ('-4.5 <= ints() <= -1.5', [-4,-3,-2]),
        ('-4.5 <= ints() <  -1.5', [-4,-3,-2]),
        ('-4.5 <  ints() <  -1.5', [-4,-3,-2]),
        ('-4.5 <  ints() <= -1.5', [-4,-3,-2]),
        ('-4.0 <= ints() <= -1.0', [-4,-3,-2,-1]),
        ('-4.0 <= ints() <  -1.0', [-4,-3,-2]),
        ('-4.0 <  ints() <  -1.0', [-3,-2]),
        ('-4.0 <  ints() <= -1.0', [-3,-2,-1]),
        ('ints() <= 4.5', [0,1,2,3,4]),
        ('ints() <  4.5', [0,1,2,3,4]),
        ('ints() <= 4.0', [0,1,2,3,4]),
        ('ints() <  4.0', [0,1,2,3]),
        ('ints() > 4.5', []),
        ('4.5 < ints()', []),
        ]
    print
    for testItem in testList:
        test(testItem)
    print






More information about the Python-list mailing list