@@ -262,6 +262,83 @@ def make_false_iterator():
262262 return iterators
263263
264264
265+ def _double_filter_thread_safe (filter_function , iterable , lazy_tuple = False ):
266+ '''
267+ (Thread-safe only in the sense that the two iterables may be consumed
268+ simultaneously by two threads; doesn't mean each iterable can be consumed
269+ simultaneously by multiple threads.)
270+ '''
271+ iterator = iter (iterable )
272+ lock_0 = threading .Lock ()
273+
274+ deque_0 = collections .deque ()
275+ deque_1 = collections .deque ()
276+
277+ deque_0_iterator = call_until_exception (deque_0 , IndexError )
278+ deque_1_iterator = call_until_exception (deque_1 , IndexError )
279+
280+ iterator_exhausted = False
281+
282+ true_deque = collections .deque ()
283+ false_deque = collections .deque ()
284+
285+
286+ def phase_0 ():
287+ with lock_0 :
288+ item = next (iterator )
289+ pair = [item , None ]
290+ deque_0 .append (pair )
291+ deque_1 .append (pair )
292+
293+ def phase_1 ():
294+ with lock_1 :
295+ try :
296+ pair = deque_1 .popleft ()
297+ except IndexError :
298+ for item , value in deque_0_iterator :
299+ if value :
300+ true_deque .append (item )
301+ else :
302+ false_deque .append (item )
303+ raise StopIteration
304+ # Reaching here (with released lock) only if we got a pair out of
305+ # `deque_1`:
306+ assert pair [1 ] is None
307+ pair [1 ] = filter_function (pair [0 ])
308+
309+
310+ def make_true_iterator ():
311+ try :
312+ while True :
313+ yield from true_deque
314+ phase_0 ()
315+ phase_1 ()
316+ except StopIteration : # Original iterator exhausted
317+
318+ yield from true_deque
319+
320+
321+
322+
323+ def make_false_iterator ():
324+ while True :
325+ try :
326+ yield false_deque .popleft ()
327+ except IndexError :
328+ value = next (iterator ) # `StopIteration` exception recycled.
329+ if filter_function (value ):
330+ true_deque .append (value )
331+ else :
332+ yield value
333+
334+ iterators = (make_true_iterator (), make_false_iterator ())
335+
336+ if lazy_tuple :
337+ from python_toolbox import nifty_collections # Avoiding circular import.
338+ return tuple (map (nifty_collections .LazyTuple , iterators ))
339+ else :
340+ return iterators
341+
265342
266343def get_ratio (filter_function , iterable ):
267344 '''Get the ratio of `iterable` items that pass `filter_function`.'''
0 commit comments