import collections
class peekable(object):
""" An iterator that supports a peek operation.
this is a merge of example 19.18 of python cookbook part 2, peek ahead more steps
and the simpler example 16.7, which peeks ahead one step and stores it in
the self.preview variable.
Adapted so the peek function never raises an error, but gives the
self.sentinel value in order to identify the exhaustion of the iter object.
Example usage:
>>> p = peekable(range(4))
>>> p.peek()
0
>>> p.next(1)
[0]
>>> p.isFirst()
True
>>> p.preview
1
>>> p.isFirst()
True
>>> p.peek(3)
[1, 2, 3]
>>> p.next(2)
[1, 2]
>>> p.peek(2) #doctest: +ELLIPSIS
[3, <object object at ...>]
>>> p.peek(1)
[3]
>>> p.next(2)
Traceback (most recent call last):
StopIteration
>>> p.next()
3
>>> p.isLast()
True
>>> p.next()
Traceback (most recent call last):
StopIteration
>>> p.next(0)
[]
>>> p.peek() #doctest: +ELLIPSIS
<object object at ...>
>>> p.preview #doctest: +ELLIPSIS
<object object at ...>
>>> p.isLast() # after the iter process p.isLast remains True
True
"""
sentinel = object() #schildwacht
def __init__(self, iterable):
self._nit = iter(iterable).next # for speed
self._iterable = iter(iterable)
self._cache = collections.deque()
self._fillcache(1) # initialize the first preview already
self.preview = self._cache[0]
self.count = -1 # keeping the count, possible to check
# isFirst and isLast status
def __iter__(self):
return self
def _fillcache(self, n):
"""fill _cache of items to come, with one extra for the preview variable
"""
if n is None:
n = 1
while len(self._cache) < n+1:
try:
Next = self._nit()
except StopIteration:
# store sentinel, to identify end of iter:
Next = self.sentinel
self._cache.append(Next)
def next(self, n=None):
"""gives next item of the iter, or a list of n items
raises StopIteration if the iter is exhausted (self.sentinel is found),
but in case of n > 1 keeps the iter alive for a smaller "next" calls
"""
self._fillcache(n)
if n is None:
result = self._cache.popleft()
if result == self.sentinel:
# find sentinel, so end of iter:
self.preview = self._cache[0]
raise StopIteration
self.count += 1
else:
result = [self._cache.popleft() for i in range(n)]
if result and result[-1] == self.sentinel:
# recache for future use:
self._cache.clear()
self._cache.extend(result)
self.preview = self._cache[0]
raise StopIteration
self.count += n
self.preview = self._cache[0]
return result
def isFirst(self):
"""returns true if iter is at first position
"""
return self.count == 0
def isLast(self):
"""returns true if iter is at last position or after StopIteration
"""
return self.preview == self.sentinel
def peek(self, n=None):
"""gives next item, without exhausting the iter, or a list of 0 or more next items
with n == None, you can also use the self.preview variable, which is the first item
to come.
"""
self._fillcache(n)
if n is None:
result = self._cache[0]
else:
result = [self._cache[i] for i in range(n)]
return result