/ 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))