1#!/usr/bin/env python3
2
3"""Various iterator related utilities."""
4
5import itertools
6import collections
7
8
9def inv_getitem(val, obj):
10 """Return obj[val]."""
11 # why?
12 return obj[val]
13
14
15def firstof(iterator):
16 """Assuming the input yields iterables, return the first of each."""
17 for i in iterator:
18 yield i[0]
19
20
21def consume(iterator, n=None):
22 """Advance the iterator n-steps ahead. If n is none, consume entirely."""
23
24 # The technique uses objects that consume iterators at C speed.
25 if n is None:
26 # feed the entire iterator into a zero-length deque
27 collections.deque(iterator, maxlen=0)
28 else:
29 # advance to the emtpy slice starting at position n
30 next(itertools.islice(iterator, n, n), None)
31
32
33def count_iterable(i):
34 """Count the number of values returned by an iterator."""
35 return sum(1 for e in i)
36
37
38def pairwise(iterable):
39 """Map s -> (s0,s1), (s1,s2), (s2, s3), ..."""
40 a, b = itertools.tee(iterable)
41 #next(b, None)
42 next(b)
43 return zip(a, b)
44
45
46def ranger(iterable, binary_pred, stop_mod=None):
47 """Return a (start,stop) tuples giving ranges found in the input series.
48 Consecutive values are considered part of a range if binary_pred(n,n+1) returns True.
49 stop_mod is added to the stop value of every output.
50 I.e. [1,2,3,6,7,10] -> [(1,3),(6,7),(10,10)] if binary_pred is b-a<=1 and stop_mode is 0."""
51
52 #logging.debug('RANGER: '+str(iterable))
53
54 def returner(stop):
55 """Return the end value of the range, modified to `stop_mod` if present."""
56 if stop_mod is None:
57 return stop
58
59 else:
60 return stop + stop_mod
61
62 prev_item = None
63 seq_start = None
64 item = None
65 for item in iterable:
66 #logging.debug('ITEM '+str(item))
67 if prev_item is not None:
68 if binary_pred(prev_item, item):
69 if seq_start is None:
70 seq_start = prev_item
71 else:
72 pass
73
74 else:
75 if seq_start is None:
76 yield prev_item, returner(prev_item)
77 else:
78 yield seq_start, returner(prev_item)
79 seq_start = None
80
81 prev_item = item
82
83 if item is not None:
84 if seq_start is None:
85 yield item, returner(item)
86 else:
87 yield seq_start, returner(item)
88
89
90def quantify(iterable, pred=bool):
91 """Count how many times the predicate is true."""
92 return sum(map(pred, iterable))
93
94
95def compose(func_1, func_2, unpack=False):
96 """The function returned by compose is a composition of func_1 and func_2.
97 That is, compose(func_1, func_2)(5) == func_1(func_2(5)).
98 """
99 if not callable(func_1):
100 raise TypeError('First argument to compose must be callable')
101
102 if not callable(func_2):
103 raise TypeError('Second argument to compose must be callable')
104
105 if unpack:
106 def composition(*args, **kwargs):
107 """Implementation passing results from inner function as separate parameters
108 to the outer function.
109 """
110 return func_1(*func_2(*args, **kwargs))
111
112 else:
113 def composition(*args, **kwargs):
114 """Implementation passing the result of the inner function literally to the
115 outer function.
116 """
117 return func_1(func_2(*args, **kwargs))
118
119 return composition