1 ## {{{ http://code.activestate.com/recipes/577361/ (r1)
2 import collections
3 class peekable(object):
4 """ An iterator that supports a peek operation.
5
6 this is a merge of example 19.18 of python cookbook part 2, peek ahead more steps
7 and the simpler example 16.7, which peeks ahead one step and stores it in
8 the self.preview variable.
9
10 Adapted so the peek function never raises an error, but gives the
11 self.sentinel value in order to identify the exhaustion of the iter object.
12
13 Example usage:
14
15 >>> p = peekable(range(4))
16 >>> p.peek()
17 0
18 >>> p.next(1)
19 [0]
20 >>> p.isFirst()
21 True
22 >>> p.preview
23 1
24 >>> p.isFirst()
25 True
26 >>> p.peek(3)
27 [1, 2, 3]
28 >>> p.next(2)
29 [1, 2]
30 >>> p.peek(2) #doctest: +ELLIPSIS
31 [3, <object object at ...>]
32 >>> p.peek(1)
33 [3]
34 >>> p.next(2)
35 Traceback (most recent call last):
36 StopIteration
37 >>> p.next()
38 3
39 >>> p.isLast()
40 True
41 >>> p.next()
42 Traceback (most recent call last):
43 StopIteration
44 >>> p.next(0)
45 []
46 >>> p.peek() #doctest: +ELLIPSIS
47 <object object at ...>
48 >>> p.preview #doctest: +ELLIPSIS
49 <object object at ...>
50 >>> p.isLast() # after the iter process p.isLast remains True
51 True
52 """
53 sentinel = object() #schildwacht
54 def __init__(self, iterable):
55 self._nit = iter(iterable).next # for speed
56 self._iterable = iter(iterable)
57 self._cache = collections.deque()
58 self._fillcache(1) # initialize the first preview already
59 self.preview = self._cache[0]
60 self.count = -1 # keeping the count, possible to check
61 # isFirst and isLast status
62 def __iter__(self):
63 return self
64 def _fillcache(self, n):
65 """fill _cache of items to come, with one extra for the preview variable
66 """
67 if n is None:
68 n = 1
69 while len(self._cache) < n+1:
70 try:
71 Next = self._nit()
72 except StopIteration:
73 # store sentinel, to identify end of iter:
74 Next = self.sentinel
75 self._cache.append(Next)
76 def next(self, n=None):
77 """gives next item of the iter, or a list of n items
78
79 raises StopIteration if the iter is exhausted (self.sentinel is found),
80 but in case of n > 1 keeps the iter alive for a smaller "next" calls
81 """
82 self._fillcache(n)
83 if n is None:
84 result = self._cache.popleft()
85 if result == self.sentinel:
86 # find sentinel, so end of iter:
87 self.preview = self._cache[0]
88 raise StopIteration
89 self.count += 1
90 else:
91 result = [self._cache.popleft() for i in range(n)]
92 if result and result[-1] == self.sentinel:
93 # recache for future use:
94 self._cache.clear()
95 self._cache.extend(result)
96 self.preview = self._cache[0]
97 raise StopIteration
98 self.count += n
99 self.preview = self._cache[0]
100 return result
101
102 def isFirst(self):
103 """returns true if iter is at first position
104 """
105 return self.count == 0
106
107 def isLast(self):
108 """returns true if iter is at last position or after StopIteration
109 """
110 return self.preview == self.sentinel
111
112 def peek(self, n=None):
113 """gives next item, without exhausting the iter, or a list of 0 or more next items
114
115 with n == None, you can also use the self.preview variable, which is the first item
116 to come.
117 """
118 self._fillcache(n)
119 if n is None:
120 result = self._cache[0]
121 else:
122 result = [self._cache[i] for i in range(n)]
123 return result
124 ## end of http://code.activestate.com/recipes/577361/ }}}