/ adafruit_itertools / adafruit_itertools_extras.py
adafruit_itertools_extras.py
1 # 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and 2 # the Individual or Organization ("Licensee") accessing and otherwise using Python 3 # 3.7.3 software in source or binary form and its associated documentation. 4 5 # 2. Subject to the terms and conditions of this License Agreement, PSF hereby 6 # grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, 7 # analyze, test, perform and/or display publicly, prepare derivative works, 8 # distribute, and otherwise use Python 3.7.3 alone or in any derivative 9 # version, provided, however, that PSF's License Agreement and PSF's notice of 10 # copyright, i.e., "Copyright © 2001-2019 Python Software Foundation; All Rights 11 # Reserved" are retained in Python 3.7.3 alone or in any derivative version 12 # prepared by Licensee. 13 14 # 3. In the event Licensee prepares a derivative work that is based on or 15 # incorporates Python 3.7.3 or any part thereof, and wants to make the 16 # derivative work available to others as provided herein, then Licensee hereby 17 # agrees to include in any such work a brief summary of the changes made to Python 18 # 3.7.3. 19 20 # 4. PSF is making Python 3.7.3 available to Licensee on an "AS IS" basis. 21 # PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF 22 # EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR 23 # WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE 24 # USE OF PYTHON 3.7.3 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 25 26 # 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.7.3 27 # FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF 28 # MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.7.3, OR ANY DERIVATIVE 29 # THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 30 31 # 6. This License Agreement will automatically terminate upon a material breach of 32 # its terms and conditions. 33 34 # 7. Nothing in this License Agreement shall be deemed to create any relationship 35 # of agency, partnership, or joint venture between PSF and Licensee. This License 36 # Agreement does not grant permission to use PSF trademarks or trade name in a 37 # trademark sense to endorse or promote products or services of Licensee, or any 38 # third party. 39 40 # 8. By copying, installing or otherwise using Python 3.7.3, Licensee agrees 41 # to be bound by the terms and conditions of this License Agreement. 42 """ 43 `adafruit_itertools_extras` 44 ================================================================================ 45 46 Extras for Python itertools adapted for CircuitPython by Dave Astels 47 48 This module contains an extended toolset using the existing itertools as 49 building blocks. 50 51 The extended tools offer the same performance as the underlying 52 toolset. The superior memory performance is kept by processing elements one at 53 a time rather than bringing the whole iterable into memory all at once. Code 54 volume is kept small by linking the tools together in a functional style which 55 helps eliminate temporary variables. High speed is retained by preferring 56 "vectorized" building blocks over the use of for-loops and generators which 57 incur interpreter overhead. 58 59 Copyright 2001-2019 Python Software Foundation; All Rights Reserved 60 61 * Author(s): The PSF and Dave Astels 62 63 Implementation Notes 64 -------------------- 65 66 Based on code from the offical Python documentation. 67 68 **Hardware:** 69 70 **Software and Dependencies:** 71 72 * Adafruit's CircuitPython port of itertools 73 * Adafruit CircuitPython firmware for the supported boards: 74 https://github.com/adafruit/circuitpython/releases 75 """ 76 77 #pylint:disable=invalid-name,deprecated-lambda,keyword-arg-before-vararg 78 79 import adafruit_itertools as it 80 81 __version__ = "0.0.0-auto.0" 82 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Itertools.git" 83 84 85 def all_equal(iterable): 86 """Returns True if all the elements are equal to each other. 87 88 :param iterable: source of values 89 90 """ 91 g = it.groupby(iterable) 92 next(g) # should succeed, value isn't relevant 93 try: 94 next(g) # should fail: only 1 group 95 return False 96 except StopIteration: 97 return True 98 99 100 def dotproduct(vec1, vec2): 101 """Compute the dot product of two vectors. 102 103 :param vec1: the first vector 104 :param vec2: the second vector 105 106 """ 107 # dotproduct([1, 2, 3], [1, 2, 3]) -> 14 108 return sum(map(lambda x, y: x * y, vec1, vec2)) 109 110 111 def first_true(iterable, default=False, pred=None): 112 """Returns the first true value in the iterable. 113 114 If no true value is found, returns *default* 115 116 If *pred* is not None, returns the first item for which pred(item) 117 is true. 118 119 :param iterable: source of values 120 :param default: the value to return if no true value is found (default is 121 False) 122 :param pred: if not None test the result of applying pred to each value 123 instead of the values themselves (default is None) 124 125 """ 126 # first_true([a,b,c], x) --> a or b or c or x 127 # first_true([a,b], x, f) --> a if f(a) else b if f(b) else x 128 try: 129 return next(filter(pred, iterable)) 130 except StopIteration: 131 return default 132 133 134 def flatten(iterable_of_iterables): 135 """Flatten one level of nesting. 136 137 :param iterable_of_iterables: a sequence of iterables to flatten 138 139 """ 140 # flatten(['ABC', 'DEF']) --> A B C D E F 141 return it.chain_from_iterable(iterable_of_iterables) 142 143 144 def grouper(iterable, n, fillvalue=None): 145 """Collect data into fixed-length chunks or blocks. 146 147 :param iterable: source of values 148 :param n: chunk size 149 :param fillvalue: value to use for filling out the final chunk. 150 Defaults to None. 151 152 """ 153 # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx 154 args = [iter(iterable)] * n 155 return it.zip_longest(*args, fillvalue=fillvalue) 156 157 158 def iter_except(func, exception): 159 """ Call a function repeatedly, yielding the results, until exception is raised. 160 161 Converts a call-until-exception interface to an iterator interface. 162 Like builtins.iter(func, sentinel) but uses an exception instead 163 of a sentinel to end the loop. 164 165 Examples: 166 iter_except(functools.partial(heappop, h), IndexError) # priority queue iterator 167 iter_except(d.popitem, KeyError) # non-blocking dict iterator 168 iter_except(d.popleft, IndexError) # non-blocking deque iterator 169 iter_except(q.get_nowait, Queue.Empty) # loop over a producer Queue 170 iter_except(s.pop, KeyError) # non-blocking set iterator 171 172 :param func: the function to call repeatedly 173 :param exception: the exception upon which to stop 174 175 """ 176 try: 177 while True: 178 yield func() 179 except exception: 180 pass 181 182 183 def ncycles(iterable, n): 184 """Returns the sequence elements a number of times. 185 186 :param iterable: the source of values 187 :param n: how many time to repeal the values 188 189 """ 190 return it.chain_from_iterable(it.repeat(tuple(iterable), n)) 191 192 193 def nth(iterable, n, default=None): 194 """Returns the nth item or a default value. 195 196 :param iterable: the source of values 197 :param n: the index of the item to fetch, starts at 0 198 199 """ 200 try: 201 return next(it.islice(iterable, n, n+1)) 202 except StopIteration: 203 return default 204 205 def padnone(iterable): 206 """Returns the sequence elements and then returns None indefinitely. 207 208 Useful for emulating the behavior of the built-in map() function. 209 210 :param iterable: the source of initial values 211 """ 212 # take(5, padnone([1, 2, 3])) -> 1 2 3 None None 213 return it.chain(iterable, it.repeat(None)) 214 215 216 def pairwise(iterable): 217 """Pair up valuesin the iterable. 218 219 :param iterable: source of values 220 221 """ 222 # pairwise(range(11)) -> (1, 2), (3, 4), (5, 6), (7, 8), (9, 10) 223 a, b = it.tee(iterable) 224 try: 225 next(b) 226 except StopIteration: 227 pass 228 return zip(a, b) 229 230 231 def partition(pred, iterable): 232 """Use a predicate to partition entries into false entries and true entries. 233 234 :param pred: the predicate that divides the values 235 :param iterable: source of values 236 237 """ 238 # partition(lambda x: x % 2, range(10)) --> 0 2 4 6 8 and 1 3 5 7 9 239 t1, t2 = it.tee(iterable) 240 return it.filterfalse(pred, t1), filter(pred, t2) 241 242 243 def prepend(value, iterator): 244 """Prepend a single value in front of an iterator 245 246 :param value: the value to prepend 247 :param iterator: the iterator to which to prepend 248 249 """ 250 # prepend(1, [2, 3, 4]) -> 1 2 3 4 251 return it.chain([value], iterator) 252 253 254 def quantify(iterable, pred=bool): 255 """Count how many times the predicate is true. 256 257 :param iterable: source of values 258 :param pred: the predicate whose result is to be quantified when applied to 259 all values in iterable. Defaults to bool() 260 261 """ 262 # quantify([2, 56, 3, 10, 85], lambda x: x >= 10) -> 3 263 return sum(map(pred, iterable)) 264 265 266 def repeatfunc(func, times=None, *args): 267 """Repeat calls to func with specified arguments. 268 269 Example: repeatfunc(random.random) 270 271 :param func: the function to be called 272 :param times: the number of times to call it: size of the resulting iterable 273 None means infinitely. Default is None. 274 275 """ 276 if times is None: 277 return it.starmap(func, it.repeat(args)) 278 return it.starmap(func, it.repeat(args, times)) 279 280 281 def roundrobin(*iterables): 282 """Return an iterable created by repeatedly picking value from each 283 argument in order. 284 285 :param args: the iterables to pick from 286 287 """ 288 # roundrobin('ABC', 'D', 'EF') --> A D E B F C 289 # Recipe credited to George Sakkis 290 num_active = len(iterables) 291 nexts = it.cycle(iter(it).__next__ for it in iterables) 292 while num_active: 293 try: 294 for n in nexts: 295 yield n() 296 except StopIteration: 297 # Remove the iterator we just exhausted from the cycle. 298 num_active -= 1 299 nexts = it.cycle(it.islice(nexts, num_active)) 300 301 302 def tabulate(function, start=0): 303 """Apply a function to a sequence of consecutive integers. 304 305 :param function: the function of one integer argument 306 :param start: optional value to start at (default is 0) 307 308 """ 309 # take(5, tabulate(lambda x: x * x))) -> 0 1 4 9 16 310 return map(function, it.count(start)) 311 312 313 def tail(n, iterable): 314 """Return an iterator over the last n items 315 316 :param n: how many values to return 317 :param iterable: the source of values 318 319 """ 320 # tail(3, 'ABCDEFG') --> E F G 321 i = iter(iterable) 322 buf = [] 323 while True: 324 try: 325 buf.append(next(i)) 326 if len(buf) > n: 327 buf.pop(0) 328 except StopIteration: 329 break 330 return iter(buf) 331 332 333 def take(n, iterable): 334 """Return first n items of the iterable as a list 335 336 :param n: how many values to take 337 :param iterable: the source of values 338 339 """ 340 # take(3, 'ABCDEF')) -> A B C 341 return list(it.islice(iterable, n))