1515infinity = float ('inf' )
1616
1717
18- def iterate_overlapping_subsequences (iterable , length = 2 , wrap_around = False ):
18+ def iterate_overlapping_subsequences (iterable , length = 2 , wrap_around = False ,
19+ lazy_tuple = False ):
1920 '''
2021 Iterate over overlapping subsequences from the iterable.
2122
@@ -28,7 +29,22 @@ def iterate_overlapping_subsequences(iterable, length=2, wrap_around=False):
2829
2930 If `wrap_around=True`, the result would be `[(0, 1, 2), (1,
3031 2, 3), (2, 3, 0), (3, 0, 1)]`.
32+
33+ If `lazy_tuple=True`, returns a `LazyTuple` rather than an iterator.
3134 '''
35+ iterator = _iterate_overlapping_subsequences (
36+ iterable = iterable , length = length , wrap_around = wrap_around
37+ )
38+
39+ if lazy_tuple :
40+ from python_toolbox import nifty_collections # Avoiding circular import.
41+ return nifty_collections .LazyTuple (iterator )
42+ else :
43+ return iterator
44+
45+
46+ def _iterate_overlapping_subsequences (iterable , length , wrap_around ):
47+
3248 if length == 1 :
3349 for item in iterable :
3450 yield item
@@ -68,15 +84,27 @@ def iterate_overlapping_subsequences(iterable, length=2, wrap_around=False):
6884 yield tuple (deque )
6985
7086
71- def shorten (iterable , n ):
87+ def shorten (iterable , n , lazy_tuple = False ):
7288 '''
7389 Shorten an iterable to length `n`.
7490
7591 Iterate over the given iterable, but stop after `n` iterations (Or when the
7692 iterable stops iteration by itself.)
7793
7894 `n` may be infinite.
95+
96+ If `lazy_tuple=True`, returns a `LazyTuple` rather than an iterator.
7997 '''
98+ iterator = _shorten (iterable = iterable , n = n )
99+
100+ if lazy_tuple :
101+ from python_toolbox import nifty_collections # Avoiding circular import.
102+ return nifty_collections .LazyTuple (iterator )
103+ else :
104+ return iterator
105+
106+
107+ def _shorten (iterable , n ):
80108
81109 if n == infinity :
82110 for thing in iterable :
@@ -94,15 +122,27 @@ def shorten(iterable, n):
94122 raise StopIteration
95123
96124
97- def enumerate (reversible , reverse_index = False ):
125+ def enumerate (reversible , reverse_index = False , lazy_tuple = False ):
98126 '''
99127 Iterate over `(i, item)` pairs, where `i` is the index number of `item`.
100128
101129 This is an extension of the builtin `enumerate`. What it allows is to get a
102130 reverse index, by specifying `reverse_index=True`. This causes `i` to count
103131 down to zero instead of up from zero, so the `i` of the last member will be
104132 zero.
133+
134+ If `lazy_tuple=True`, returns a `LazyTuple` rather than an iterator.
105135 '''
136+ iterator = _enumerate (reversible = reversible , reverse_index = reverse_index )
137+
138+ if lazy_tuple :
139+ from python_toolbox import nifty_collections # Avoiding circular import.
140+ return nifty_collections .LazyTuple (iterator )
141+ else :
142+ return iterator
143+
144+
145+ def _enumerate (reversible , reverse_index ):
106146 if reverse_index is False :
107147 return __builtin__ .enumerate (reversible )
108148 else :
@@ -136,20 +176,31 @@ def get_length(iterable):
136176 return i
137177
138178
139- def iter_with (iterable , context_manager ):
140- '''Iterate on `iterable`, `with`ing the context manager on every `next`.'''
179+ def iter_with (iterable , context_manager , lazy_tuple = False ):
180+ '''
181+ Iterate on `iterable`, `with`ing the context manager on every `next`.
182+
183+ If `lazy_tuple=True`, returns a `LazyTuple` rather than an iterator.
184+ '''
185+ iterator = _iter_with (iterable = iterable , context_manager = context_manager )
186+
187+ if lazy_tuple :
188+ from python_toolbox import nifty_collections # Avoiding circular import.
189+ return nifty_collections .LazyTuple (iterator )
190+ else :
191+ return iterator
192+
193+
194+ def _iter_with (iterable , context_manager ):
141195
142196 iterator = iter (iterable )
143197
144198 while True :
145199
146200 with context_manager :
147201 next_item = next (iterator )
148- # You may notice that we are not `except`ing a `StopIteration`
149- # here; If we get one, it'll just get propagated and end *this*
150- # iterator. todo: I just realized this will probably cause a bug
151- # where `__exit__` will get the `StopIteration`! Make failing tests
152- # and fix.
202+ # Recycling `StopIteration` exception. (Assuming the context
203+ # manager doesn't have special treatment for it.)
153204
154205 yield next_item
155206
@@ -167,13 +218,18 @@ def get_items(iterable, n, container_type=tuple):
167218 return container_type (shorten (iterable , n ))
168219
169220
170- def double_filter (filter_function , iterable ):
221+ def double_filter (filter_function , iterable , lazy_tuple = False ):
171222 '''
172- Filter an `iterable` into two lists according to a `filter_function`.
223+ Filter an `iterable` into two iterables according to a `filter_function`.
173224
174225 This is similar to the builtin `filter`, except it returns a tuple of two
175226 iterators, the first iterating on items that passed the filter function,
176227 and the second iterating on items that didn't.
228+
229+ Note that this function is not thread-safe. (You may not consume the two
230+ iterators on two separate threads.)
231+
232+ If `lazy_tuple=True`, returns a `LazyTuple` rather than an iterator.
177233 '''
178234 iterator = iter (iterable )
179235
@@ -202,7 +258,13 @@ def make_false_iterator():
202258 else :
203259 yield value
204260
205- return (make_true_iterator (), make_false_iterator ())
261+ iterators = (make_true_iterator (), make_false_iterator ())
262+
263+ if lazy_tuple :
264+ from python_toolbox import nifty_collections # Avoiding circular import.
265+ return tuple (map (nifty_collections .LazyTuple , iterators ))
266+ else :
267+ return iterators
206268
207269
208270
@@ -221,7 +283,7 @@ def get_ratio(filter_function, iterable):
221283
222284
223285def fill (iterable , fill_value = None , fill_value_maker = None , length = infinity ,
224- sequence_type = None ):
286+ sequence_type = None , lazy_tuple = False ):
225287 '''
226288 Iterate on `iterable`, and after it's exhaused, yield fill values.
227289
@@ -232,19 +294,26 @@ def fill(iterable, fill_value=None, fill_value_maker=None, length=infinity,
232294 If `length` is given, shortens the iterator to that length.
233295
234296 If `sequence_type` is given, instead of returning an iterator, this
235- function will return a sequence of that type.
297+ function will return a sequence of that type. If `lazy_tuple=True`, uses a
298+ `LazyTuple`. (Can't use both options together.)
236299 '''
300+ # Validating user input:
301+ assert (sequence_type is None ) or (lazy_tuple is False )
302+
237303 iterator = _fill (iterable , fill_value = fill_value ,
238304 fill_value_maker = fill_value_maker ,
239305 length = length )
240306
241- if sequence_type is None :
307+ if lazy_tuple :
308+ from python_toolbox import nifty_collections # Avoiding circular import.
309+ return nifty_collections .LazyTuple (iterator )
310+ elif sequence_type is None :
242311 return iterator
243312 else :
244313 return sequence_type (iterator )
245314
246315
247- def _fill (iterable , fill_value = None , fill_value_maker = None , length = infinity ):
316+ def _fill (iterable , fill_value , fill_value_maker , length ):
248317 if fill_value_maker is not None :
249318 assert fill_value is None
250319 else :
@@ -267,6 +336,30 @@ def _fill(iterable, fill_value=None, fill_value_maker=None, length=infinity):
267336 yield fill_value_maker ()
268337
269338
339+ def call_until_exception (function , exception , lazy_tuple = False ):
340+ '''
341+ Iterate on values returned from `function` until getting `exception`.
342+
343+ If `lazy_tuple=True`, returns a `LazyTuple` rather than an iterator.
344+ '''
345+ iterator = _call_until_exception (function , exception )
346+ if lazy_tuple :
347+ from python_toolbox import nifty_collections # Avoiding circular import.
348+ return nifty_collections .LazyTuple (iterator )
349+ else :
350+ return iterator
351+
352+
353+ def _call_until_exception (function , exception ):
354+ from python_toolbox import sequence_tools
355+ exceptions = sequence_tools .to_tuple (exception , item_type = type )
356+ try :
357+ while True :
358+ yield function ()
359+ except exceptions :
360+ raise StopIteration
361+
362+
270363def get_single_if_any (iterable ,
271364 exception_on_multiple = Exception ('More than one value '
272365 'not allowed.' )):
0 commit comments