Absolutely: ever since you brought up the Hamming sequence I've been interested in this approach. However, if iterators could be extended in place, these solutions would be even more attractive.""" ================================================================================ Example 8 ================================================================================ Running after your tail with itertools.tee
The beauty of it is that recursive running after their tail FP algorithms are quite straightforwardly expressed with this Python idiom. """
def Ex8_Fibonacci():
print "Entering Ex8_Fibonacci"
def _Ex8_Fibonacci():
print "Entering _Ex8_Fibonacci"
yield 1
yield 1
fibTail.next() # Skip the first one
for n in (head + tail for (head, tail) in izip(fibHead, fibTail)):
yield n
fibHead, fibTail, fibRes = tee(_Ex8_Fibonacci(), 3)
return fibRes
print sEx8Doc
print list(islice(Ex8_Fibonacci(), 5))
Here are some examples for infinite series constructed with an extendable iterator. This iterator is returned by an iterable class 'Stream', shown below the examples:
def factorial(): """ >>> f = factorial() >>> f.tolist(10) [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880] """
factorial = Stream([1]) factorial.extend(factorial * it.count(1))
return factorial
def fib(): """Example: >>> f = fib() >>> f.tolist(10) [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]""" fib = Stream([1,1]) fib.extend(x+y for x, y in it.izip(fib, fib[1:])) return fib
def multimerge(*iterables): """Yields the items in iterables in order, without duplicates""" cache = {} iterators = map(iter,iterables) number = len(iterables) exhausted = 0 while 1: for it in iterators: try: cache.setdefault(it.next(),[]).append(it) except StopIteration: exhausted += 1 if exhausted == number: raise StopIteration val = min(cache) iterators = cache.pop(val) yield val
def hamming():
"""
Example:
>>> h = hamming()
>>> list(h[20:40])
[40, 45, 48, 50, 54, 60, 64, 72, 75, 80, 81, 90, 96, 100, 108, 120, 125, 128, 135, 144]
>>> h[10000]
288555831593533440L
"""
hamming = Stream([1]) hamming.extend(i for i in multimerge(2 * hamming, 3 * hamming, 5 * hamming)) return hamming
def compounds():
"""Extension of Hamming series to compounds of primes(2..13)
Example:
>>> c = compounds()
>>> list(c[20:30])
[24, 25, 26, 27, 28, 30, 32, 33, 35, 36]"""
compounds = Stream([1])
compounds.extend(i for i in multimerge(2 * compounds, 3 * compounds, 5 * compounds, 7 * compounds, 9 * compounds, 11 * compounds, 13 * compounds))
return compounds
# Stream class for the above examples:
import itertools as it import operator as op
class Stream(object): """Provides an indepent iterator (using tee) on every iteration request Also implements lazy iterator arithmetic"""
def __init__(self, *iterables, **kw):
"""iterables: tuple of iterables (including iterators). A sequence of
iterables will be chained
kw: not used in this base class"""
self.queue = list(iterables)
self.itertee = it.tee(self._chain(self.queue))[0] # We may not need this in every case
def extend(self,other): """extend(other: iterable) => None appends iterable to the end of the Stream instance """ self.queue.append(other)
def _chain(self, queue): while queue: for i in self.queue.pop(0): self.head = i yield i
# Iterator methods:
def __iter__(self): """Normal iteration over the iterables in self.queue in turn""" return self.itertee.__copy__()
def _binop(self,other,op): """See injected methods - __add__, __mul__ etc..""" if hasattr(other,"__iter__"): return (op(i,j) for i, j in it.izip(self,other)) else: return (op(i,other) for i in self)
def __getitem__(self,index): """__getitem__(index: integer | slice) index: integer => element at position index index: slice if slice.stop is given => Stream(it.islice(iter(self), index.start, index.stop, index.step or 1))) else: consumes self up to start then => Stream(iter(self)) Note slice.step is ignored in this case """ if isinstance(index, slice): if index.stop: return (it.islice(iter(self), index.start or 0, index.stop, index.step or 1)) else: iterator = iter(self) for i in range(index.start): iterator.next() return iterator else: return it.islice(iter(self), index,index+1).next()
def getattr(self,attrname): """__getattr__/getattr(attrname: string) => Stream(getattr(item,attrname) for item in self) Use the getattr variant if the attrname is shadowed by the Stream class"""
return (getattr(item,attrname) for item in self) __getattr__ = getattr
# Representational methods
def tolist(self, maxlen = 100): return list(it.islice(iter(self),maxlen))
def __repr__(self): return "Stream instance at:%x: %s" % (id(self), repr(self.queue))
# Inject special methods for binary operations: _binopdoc = """%(func)s(other: constant | iterable, op: binary function) other: constant => Stream(op.%(op)s(i,other) for i in self)) other: iterable => Stream(op.%(op)s(i,j) for i, j in it.izip(self,other)) """
[setattr(Stream,attr, func(argspec = "self, other", expr = "self._binop(other, op.%s)" % opfunc, doc=_binopdoc % {"func":attr, "op":opfunc}, name=attr) ) for attr, opfunc in { "__mul__":"mul", "__add__":"add", "__sub__":"sub", "__div__":"div", "__mod__":"mod", "__pow__":"pow", }.items() ] # End inject special methods
-- http://mail.python.org/mailman/listinfo/python-list