[Edu-sig] Once more around the block (generators)

Kirby Urner pdx4d@teleport.com
Mon, 23 Jul 2001 16:33:31 -0700


The iterator type and generator classes must support
next() -- you must be able to return the next element.
Whether an iterator class also supports __getitem__
or previous() or other such methods is up to the
programmer.

Below, I turn what had been the primes() generator
function into the Primes class.  The generator becomes
a private, internal class method, and is what the class
returns when asked for an iterator (__iter__).
Calls to next() either trigger this iterator (stored
in self.gen), or return values from territory already
covered.

Given Primes is a class, you'll be begetting one or
more instantiated objects.  Each keeps track of an
internal pointer, specific to the object, in self.current.
obj.previous() is relative to where the pointer is,
currently.  obj[n] resets the pointer to n (perhaps
triggering next() a number of times in the process),
while obj.last() sets the pointer to the highest value
the pointer has ever reached.  But in the background,
all instantiations of Primes share the same class
variable Primes.pr (a growing list of primes).  This
means that if one object computes the 10,000th prime,
then all objects have access to the result i.e. don't
need to recompute it.

Having the different instantiations share a common
Primes.pr while maintaining their own specific pointers
proved an interesting challenge.

In action:

   >>> from play import *
   >>> pr1 = Primes()
   >>> pr1[10]
   31
   >>> pr2 = Primes()
   >>> pr2.pr
   [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
   >>> pr2.last()
   2
   >>> pr2.skip(4)
   11
   >>> pr2.current
   4
   >>> pr2.last()
   11
   >>> pr2[15]
   53
   >>> pr2.pr
   [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53]
   >>> pr2.last()
   53
   >>> pr1.pr
   [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53]
   >>> pr1.last()
   31
   >>> pr1.skip(25)
   127
   >>> pr1.pr
   [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 37, 41,
   43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109,
   113, 127]
   >>> pr2.skip(1)
   37
   >>> pr2.last()
   37
   >>> pr2.first()
   2

Source appended.

Kirby

==========

from __future__ import generators

class Primes:

     pr = [2]

     def __init__(self):
         self.gen = self.__generator()
         self.current = 0
         self.max = 0

     def __iter__(self):
         return self

     def next(self):
         self.current += 1
         self.max = max(self.max,self.current)
         if self.current > len(Primes.pr)-1:
             return self.gen.next()
         else:
             return Primes.pr[self.current]

     def previous(self):
         self.current -= 1
         return self.pr[self.current]

     def first(self):
         self.current = 0
         return Primes.pr[self.current]

     def last(self):
         self.current = self.max
         return Primes.pr[self.current]

     def skip(self,n):
         # pointer must be >= 0
         return self.__getitem__(max(0,self.current + n))

     def __getitem__(self,n):
         if n<0:  raise ValueError,"Out of range"
         if n > len(Primes.pr)-1:
             self.current = self.max
             while n > self.current:
                 self.next()
         else:
             self.current = n
             self.max = max(self.max,self.current)
         return Primes.pr[self.current]

     def __generator(self):
         """
         Iterator:
         Yield next higher prime, accruing successive primes in
         class variable pr
         """
         i = Primes.pr[-1]
         if i==2:
             i -= 1
         new = 0
         while 1:
            try:
                i += 2
            except OverflowError:
                i += 2L
            if new:
                yield Primes.pr[-1]
                new = 0
            for t in Primes.pr:
                if t*t > i:
                    # if t**2>candidate, we have a new prime
                    Primes.pr.append(i)
                    new = 1
                    break
                if i%t == 0:
                    # test divide by primes so far,
                    # move to next odd whenever remainder=0
                    break