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

a merge of recipes 16.7 and 19.18 of the python cookbook (2nd edition).

you can peek ahead more steps if needed, or just use the preview variable for the next item of the list.

Python, 122 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
110
111
112
113
114
115
116
117
118
119
120
121
122
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

I was using the p.preview variable in most cases, but needed to look ahead more steps occasionally. So I came up with this merge of two recipes.

The peek function never raises an error, only the next function does.

Use the sentinel value to indicate the exhaustion of the iterator when peeking ahead.

Usable variables if the instance is called p: p.preview: either the next value or p.sentinel (if you are at the last value of the iterator)

Usable functions: p.next() normal next operator p.next(n) gives a list of n next items of the iterator p.peek() gives next item (same as p.preview) p.peek(n) gives a list of n next items. If list is exhausted, p.sentinel is taken, so length of the returned list is always n p.isFirst() True if iterator is at first value p.isLast() True if iterator is at last value (or exhausted)

Quintijn Hoogenboom, august 2010

Created by Quintijn Hoogenboom on Tue, 17 Aug 2010 (MIT)
Python recipes (4591)
Quintijn Hoogenboom's recipes (1)

Required Modules

  • (none specified)

Other Information and Tasks