From 1aff7eb85d72b338a35efc07ee8a6b157e17901f Mon Sep 17 00:00:00 2001 From: Labrys of Knossos Date: Tue, 29 Nov 2022 01:21:38 -0500 Subject: [PATCH] Updated zipp to 2.0.1 --- libs/common/bin/beet.exe | Bin 108369 -> 108369 bytes libs/common/bin/chardetect.exe | Bin 108383 -> 108383 bytes libs/common/bin/easy_install-3.7.exe | Bin 108392 -> 108392 bytes libs/common/bin/easy_install.exe | Bin 108392 -> 108392 bytes libs/common/bin/guessit.exe | Bin 108377 -> 108377 bytes libs/common/bin/mid3cp.exe | Bin 108396 -> 108396 bytes libs/common/bin/mid3iconv.exe | Bin 108399 -> 108399 bytes libs/common/bin/mid3v2.exe | Bin 108396 -> 108396 bytes libs/common/bin/moggsplit.exe | Bin 108399 -> 108399 bytes libs/common/bin/mutagen-inspect.exe | Bin 108405 -> 108405 bytes libs/common/bin/mutagen-pony.exe | Bin 108402 -> 108402 bytes libs/common/bin/pbr.exe | Bin 108373 -> 108373 bytes libs/common/bin/srt.exe | Bin 108375 -> 108375 bytes libs/common/bin/subliminal.exe | Bin 108387 -> 108387 bytes libs/common/bin/unidecode.exe | Bin 108375 -> 108375 bytes .../jellyfish/cjellyfish.cp37-win_amd64.pyd | Bin 31232 -> 0 bytes libs/common/more_itertools/__init__.py | 6 + libs/common/more_itertools/__init__.pyi | 2 + libs/common/more_itertools/more.py | 4347 +++++++++++++++++ libs/common/more_itertools/more.pyi | 674 +++ libs/common/more_itertools/py.typed | 0 libs/common/more_itertools/recipes.py | 841 ++++ libs/common/more_itertools/recipes.pyi | 110 + libs/common/yaml/_yaml.cp37-win_amd64.pyd | Bin 264192 -> 0 bytes libs/common/zipp.py | 220 + libs/common/zipp/__init__.py | 381 -- libs/common/zipp/py310compat.py | 12 - 27 files changed, 6200 insertions(+), 393 deletions(-) delete mode 100644 libs/common/jellyfish/cjellyfish.cp37-win_amd64.pyd create mode 100644 libs/common/more_itertools/__init__.py create mode 100644 libs/common/more_itertools/__init__.pyi create mode 100644 libs/common/more_itertools/more.py create mode 100644 libs/common/more_itertools/more.pyi create mode 100644 libs/common/more_itertools/py.typed create mode 100644 libs/common/more_itertools/recipes.py create mode 100644 libs/common/more_itertools/recipes.pyi delete mode 100644 libs/common/yaml/_yaml.cp37-win_amd64.pyd create mode 100644 libs/common/zipp.py delete mode 100644 libs/common/zipp/__init__.py delete mode 100644 libs/common/zipp/py310compat.py diff --git a/libs/common/bin/beet.exe b/libs/common/bin/beet.exe index 1bc460b0a8c83b0465082eeffc557174c09871cf..b749e3ea4c425fbcdcd83832694fb0b4b19b868b 100644 GIT binary patch delta 27 hcmcb3j_u+(wuUW?>dTnAxu$C_V|)st1(q{90sy1Z3ReIC delta 27 hcmcb3j_u+(wuUW?>dTm3FizK8#`qLO3oK`J1OTfX3jY8A diff --git a/libs/common/bin/chardetect.exe b/libs/common/bin/chardetect.exe index 78c517dd81b867fe2ff31f5389d5d65173cbccd0..01d5a18f7f534aa4319da9dd996705d554681347 100644 GIT binary patch delta 27 hcmcbAj_v+AwuUW?>dTnAxTb3^WBd%F<(4x#0syBx3XK2& delta 27 hcmcbAj_v+AwuUW?>dTm(GfvlB#`qaT%PnVg1OTpv3pD@$ diff --git a/libs/common/bin/easy_install-3.7.exe b/libs/common/bin/easy_install-3.7.exe index f5a3bb997024f5ee812d59102ed6ebeb28368f4c..04194cd016fc9200017a2512f82baddd8478e5a0 100644 GIT binary patch delta 27 hcmaEHj_t)cwuUW?>dTlqxu$C_WBdc6)t56m0syII3adTm(F;3T9#`p(Bt1oAC1OTwG3s(RD diff --git a/libs/common/bin/easy_install.exe b/libs/common/bin/easy_install.exe index f5a3bb997024f5ee812d59102ed6ebeb28368f4c..04194cd016fc9200017a2512f82baddd8478e5a0 100644 GIT binary patch delta 27 hcmaEHj_t)cwuUW?>dTlqxu$C_WBdc6)t56m0syII3adTm(F;3T9#`p(Bt1oAC1OTwG3s(RD diff --git a/libs/common/bin/guessit.exe b/libs/common/bin/guessit.exe index cf84d1fe1170f676fc9656ad1eade4a563e40884..15e22527078866836b3d34ef9a67cdbe5b65dff6 100644 GIT binary patch delta 27 hcmcb4j_u|-wuUW?>dTnAxu$C_V|)vuC6+Tf0sy7b3U>ei delta 27 hcmcb4j_u|-wuUW?>dTm3FizK8#`qRQODtz}1OTlZ3m*Ug diff --git a/libs/common/bin/mid3cp.exe b/libs/common/bin/mid3cp.exe index 0f67347730dd3507159e7d586a0927664ba060da..9a70d062dfbffba14ef55f050778784888d6a1a0 100644 GIT binary patch delta 27 icmaEJj_u7kwuUW?>dTlqxt41#V`K!<)3ui~IsyQqKMBwP delta 27 icmaEJj_u7kwuUW?>dTm(GcMO$#>fbyr)w`~bOZpYp9*>a diff --git a/libs/common/bin/mid3iconv.exe b/libs/common/bin/mid3iconv.exe index 48cdf01e2eceb14398ffa15fa55c642753ac0b02..44ea559e00c2b89c92c2bc7d97be237964ef0fec 100644 GIT binary patch delta 28 jcmaEVj_v(9wuUW?>dTlqxwdOAV`OFoGN$V-XLJMrt9J?P delta 28 jcmaEVj_v(9wuUW?>dTm(Gj7*h#>mVFWK7pv&gcjLvn&dh diff --git a/libs/common/bin/mid3v2.exe b/libs/common/bin/mid3v2.exe index 5c4ef10859c5495e6cf882ca8bb6f41aad1985cb..38e087cf870527693520df767bc27454f14e63e7 100644 GIT binary patch delta 27 icmaEJj_u7kwuUW?>dTlqxt41#V`K!<)3ui~IsyQqKMBwP delta 27 icmaEJj_u7kwuUW?>dTm(GcMO$#>fbyr)w`~bOZpYp9*>a diff --git a/libs/common/bin/moggsplit.exe b/libs/common/bin/moggsplit.exe index 52f3e09f3d909ed105bea17210023cccbfda8e4b..060ba5d6815b30b6bafbb148527e9a4fb25a5b6c 100644 GIT binary patch delta 28 jcmaEVj_v(9wuUW?>dTlqxwdOAV`OFoGN$V-XLJMrt9J?P delta 28 jcmaEVj_v(9wuUW?>dTm(Gj7*h#>mVFWK7pv&gcjLvn&dh diff --git a/libs/common/bin/mutagen-inspect.exe b/libs/common/bin/mutagen-inspect.exe index 612fb40cff6e0f767555d6a85b3e69cd9608960d..639e3339a506e4852fc491aff869718e9bcc6c48 100644 GIT binary patch delta 28 jcmex*j_vC?wuUW?>dTlqxwdOAW8`E6GNzj>XLJMrtqBSQ delta 28 jcmex*j_vC?wuUW?>dTm(Gj7*h#>mMCWK1_%&gcjLw7v?i diff --git a/libs/common/bin/mutagen-pony.exe b/libs/common/bin/mutagen-pony.exe index e6d375a39f62a618c49b15e760afc71eecf642b1..9961a3583fb8356b2f836be2655dcf1e9c99e8c3 100644 GIT binary patch delta 28 jcmex#j_uPqwuUW?>dTlqxwdOAV`O6lGNv0YXLJMrtUw9) delta 28 jcmex#j_uPqwuUW?>dTm(Gj7*h#>mD9WK1_)&gcjLv-Jw1 diff --git a/libs/common/bin/pbr.exe b/libs/common/bin/pbr.exe index c08bb4d71277246e80a4cba90f50ebd24d4c7197..28e739c3e226fa1c8ebcbdd7b9db0c9f7b5dbe0e 100644 GIT binary patch delta 27 hcmcb5j_v9>wuUW?>dTlqxu$C_V|)psMV2!<0sy3@3S$5O delta 27 hcmcb5j_v9>wuUW?>dTm(F;3T9#`qFMi!5h!1OTh>3kv`M diff --git a/libs/common/bin/srt.exe b/libs/common/bin/srt.exe index eb48088fc38255dd63119303915886dbdfcd52ae..f3a3e58618d90890d6ac531abb178bc752821fc1 100644 GIT binary patch delta 27 hcmcb9j_vw6wuUW?>dTnAxTb3^V|)#w#g;QV0sy5v3T*%Y delta 27 hcmcb9j_vw6wuUW?>dTm(GfvlB#`qdUi!En#1OTjt3l#tW diff --git a/libs/common/bin/subliminal.exe b/libs/common/bin/subliminal.exe index 2c8e7fab1f6ca7abf4eb07e64d2ce267a3f78542..e4144d13367cd0a6020513979dcacc0f27af2273 100644 GIT binary patch delta 27 hcmaESj_vU|wuUW?>dTnAxu$C_WBd-Hm6kI)0syE|3ZDP~ delta 27 hcmaESj_vU|wuUW?>dTm3GEUcA#`qmXD=lYq1OTtH3rPR~ diff --git a/libs/common/bin/unidecode.exe b/libs/common/bin/unidecode.exe index 2dfd0da4f14613dc6032768bb3687530470aab20..8fe25e42d7ba78c322ad95e55a256a25194dfecd 100644 GIT binary patch delta 27 hcmcb9j_vw6wuUW?>dTlqxu$C_V|)#w#g;QV0sy5Z3TprW delta 27 hcmcb9j_vw6wuUW?>dTm(F;3T9#`qdUi!En#1OTjX3ljhU diff --git a/libs/common/jellyfish/cjellyfish.cp37-win_amd64.pyd b/libs/common/jellyfish/cjellyfish.cp37-win_amd64.pyd deleted file mode 100644 index cc4067cfdc220c870e2806292f844abad9e6156c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31232 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 diff --git a/libs/common/more_itertools/__init__.py b/libs/common/more_itertools/__init__.py new file mode 100644 index 00000000..557bfc20 --- /dev/null +++ b/libs/common/more_itertools/__init__.py @@ -0,0 +1,6 @@ +"""More routines for operating on iterables, beyond itertools""" + +from .more import * # noqa +from .recipes import * # noqa + +__version__ = '9.0.0' diff --git a/libs/common/more_itertools/__init__.pyi b/libs/common/more_itertools/__init__.pyi new file mode 100644 index 00000000..96f6e36c --- /dev/null +++ b/libs/common/more_itertools/__init__.pyi @@ -0,0 +1,2 @@ +from .more import * +from .recipes import * diff --git a/libs/common/more_itertools/more.py b/libs/common/more_itertools/more.py new file mode 100644 index 00000000..7f73d6ed --- /dev/null +++ b/libs/common/more_itertools/more.py @@ -0,0 +1,4347 @@ +import warnings + +from collections import Counter, defaultdict, deque, abc +from collections.abc import Sequence +from functools import partial, reduce, wraps +from heapq import heapify, heapreplace, heappop +from itertools import ( + chain, + compress, + count, + cycle, + dropwhile, + groupby, + islice, + repeat, + starmap, + takewhile, + tee, + zip_longest, +) +from math import exp, factorial, floor, log +from queue import Empty, Queue +from random import random, randrange, uniform +from operator import itemgetter, mul, sub, gt, lt, ge, le +from sys import hexversion, maxsize +from time import monotonic + +from .recipes import ( + _marker, + _zip_equal, + UnequalIterablesError, + consume, + flatten, + pairwise, + powerset, + take, + unique_everseen, + all_equal, +) + +__all__ = [ + 'AbortThread', + 'SequenceView', + 'UnequalIterablesError', + 'adjacent', + 'all_unique', + 'always_iterable', + 'always_reversible', + 'bucket', + 'callback_iter', + 'chunked', + 'chunked_even', + 'circular_shifts', + 'collapse', + 'combination_index', + 'consecutive_groups', + 'constrained_batches', + 'consumer', + 'count_cycle', + 'countable', + 'difference', + 'distinct_combinations', + 'distinct_permutations', + 'distribute', + 'divide', + 'duplicates_everseen', + 'duplicates_justseen', + 'exactly_n', + 'filter_except', + 'first', + 'groupby_transform', + 'ichunked', + 'iequals', + 'ilen', + 'interleave', + 'interleave_evenly', + 'interleave_longest', + 'intersperse', + 'is_sorted', + 'islice_extended', + 'iterate', + 'last', + 'locate', + 'longest_common_prefix', + 'lstrip', + 'make_decorator', + 'map_except', + 'map_if', + 'map_reduce', + 'mark_ends', + 'minmax', + 'nth_or_last', + 'nth_permutation', + 'nth_product', + 'numeric_range', + 'one', + 'only', + 'padded', + 'partitions', + 'peekable', + 'permutation_index', + 'product_index', + 'raise_', + 'repeat_each', + 'repeat_last', + 'replace', + 'rlocate', + 'rstrip', + 'run_length', + 'sample', + 'seekable', + 'set_partitions', + 'side_effect', + 'sliced', + 'sort_together', + 'split_after', + 'split_at', + 'split_before', + 'split_into', + 'split_when', + 'spy', + 'stagger', + 'strip', + 'strictly_n', + 'substrings', + 'substrings_indexes', + 'time_limited', + 'unique_in_window', + 'unique_to_each', + 'unzip', + 'value_chain', + 'windowed', + 'windowed_complete', + 'with_iter', + 'zip_broadcast', + 'zip_equal', + 'zip_offset', +] + + +def chunked(iterable, n, strict=False): + """Break *iterable* into lists of length *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6], 3)) + [[1, 2, 3], [4, 5, 6]] + + By the default, the last yielded list will have fewer than *n* elements + if the length of *iterable* is not divisible by *n*: + + >>> list(chunked([1, 2, 3, 4, 5, 6, 7, 8], 3)) + [[1, 2, 3], [4, 5, 6], [7, 8]] + + To use a fill-in value instead, see the :func:`grouper` recipe. + + If the length of *iterable* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + list is yielded. + + """ + iterator = iter(partial(take, n, iter(iterable)), []) + if strict: + if n is None: + raise ValueError('n must not be None when using strict mode.') + + def ret(): + for chunk in iterator: + if len(chunk) != n: + raise ValueError('iterable is not divisible by n.') + yield chunk + + return iter(ret()) + else: + return iterator + + +def first(iterable, default=_marker): + """Return the first item of *iterable*, or *default* if *iterable* is + empty. + + >>> first([0, 1, 2, 3]) + 0 + >>> first([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + + :func:`first` is useful when you have a generator of expensive-to-retrieve + values and want any arbitrary one. It is marginally shorter than + ``next(iter(iterable), default)``. + + """ + try: + return next(iter(iterable)) + except StopIteration as e: + if default is _marker: + raise ValueError( + 'first() was called on an empty iterable, and no ' + 'default value was provided.' + ) from e + return default + + +def last(iterable, default=_marker): + """Return the last item of *iterable*, or *default* if *iterable* is + empty. + + >>> last([0, 1, 2, 3]) + 3 + >>> last([], 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + try: + if isinstance(iterable, Sequence): + return iterable[-1] + # Work around https://bugs.python.org/issue38525 + elif hasattr(iterable, '__reversed__') and (hexversion != 0x030800F0): + return next(reversed(iterable)) + else: + return deque(iterable, maxlen=1)[-1] + except (IndexError, TypeError, StopIteration): + if default is _marker: + raise ValueError( + 'last() was called on an empty iterable, and no default was ' + 'provided.' + ) + return default + + +def nth_or_last(iterable, n, default=_marker): + """Return the nth or the last item of *iterable*, + or *default* if *iterable* is empty. + + >>> nth_or_last([0, 1, 2, 3], 2) + 2 + >>> nth_or_last([0, 1], 2) + 1 + >>> nth_or_last([], 0, 'some default') + 'some default' + + If *default* is not provided and there are no items in the iterable, + raise ``ValueError``. + """ + return last(islice(iterable, n + 1), default=default) + + +class peekable: + """Wrap an iterator to allow lookahead and prepending elements. + + Call :meth:`peek` on the result to get the value that will be returned + by :func:`next`. This won't advance the iterator: + + >>> p = peekable(['a', 'b']) + >>> p.peek() + 'a' + >>> next(p) + 'a' + + Pass :meth:`peek` a default value to return that instead of raising + ``StopIteration`` when the iterator is exhausted. + + >>> p = peekable([]) + >>> p.peek('hi') + 'hi' + + peekables also offer a :meth:`prepend` method, which "inserts" items + at the head of the iterable: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> p.peek() + 11 + >>> list(p) + [11, 12, 1, 2, 3] + + peekables can be indexed. Index 0 is the item that will be returned by + :func:`next`, index 1 is the item after that, and so on: + The values up to the given index will be cached. + + >>> p = peekable(['a', 'b', 'c', 'd']) + >>> p[0] + 'a' + >>> p[1] + 'b' + >>> next(p) + 'a' + + Negative indexes are supported, but be aware that they will cache the + remaining items in the source iterator, which may require significant + storage. + + To check whether a peekable is exhausted, check its truth value: + + >>> p = peekable(['a', 'b']) + >>> if p: # peekable has items + ... list(p) + ['a', 'b'] + >>> if not p: # peekable is exhausted + ... list(p) + [] + + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self._cache = deque() + + def __iter__(self): + return self + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + """Return the item that will be next returned from ``next()``. + + Return ``default`` if there are no items left. If ``default`` is not + provided, raise ``StopIteration``. + + """ + if not self._cache: + try: + self._cache.append(next(self._it)) + except StopIteration: + if default is _marker: + raise + return default + return self._cache[0] + + def prepend(self, *items): + """Stack up items to be the next ones returned from ``next()`` or + ``self.peek()``. The items will be returned in + first in, first out order:: + + >>> p = peekable([1, 2, 3]) + >>> p.prepend(10, 11, 12) + >>> next(p) + 10 + >>> list(p) + [11, 12, 1, 2, 3] + + It is possible, by prepending items, to "resurrect" a peekable that + previously raised ``StopIteration``. + + >>> p = peekable([]) + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + >>> p.prepend(1) + >>> next(p) + 1 + >>> next(p) + Traceback (most recent call last): + ... + StopIteration + + """ + self._cache.extendleft(reversed(items)) + + def __next__(self): + if self._cache: + return self._cache.popleft() + + return next(self._it) + + def _get_slice(self, index): + # Normalize the slice's arguments + step = 1 if (index.step is None) else index.step + if step > 0: + start = 0 if (index.start is None) else index.start + stop = maxsize if (index.stop is None) else index.stop + elif step < 0: + start = -1 if (index.start is None) else index.start + stop = (-maxsize - 1) if (index.stop is None) else index.stop + else: + raise ValueError('slice step cannot be zero') + + # If either the start or stop index is negative, we'll need to cache + # the rest of the iterable in order to slice from the right side. + if (start < 0) or (stop < 0): + self._cache.extend(self._it) + # Otherwise we'll need to find the rightmost index and cache to that + # point. + else: + n = min(max(start, stop) + 1, maxsize) + cache_len = len(self._cache) + if n >= cache_len: + self._cache.extend(islice(self._it, n - cache_len)) + + return list(self._cache)[index] + + def __getitem__(self, index): + if isinstance(index, slice): + return self._get_slice(index) + + cache_len = len(self._cache) + if index < 0: + self._cache.extend(self._it) + elif index >= cache_len: + self._cache.extend(islice(self._it, index + 1 - cache_len)) + + return self._cache[index] + + +def consumer(func): + """Decorator that automatically advances a PEP-342-style "reverse iterator" + to its first yield point so you don't have to call ``next()`` on it + manually. + + >>> @consumer + ... def tally(): + ... i = 0 + ... while True: + ... print('Thing number %s is %s.' % (i, (yield))) + ... i += 1 + ... + >>> t = tally() + >>> t.send('red') + Thing number 0 is red. + >>> t.send('fish') + Thing number 1 is fish. + + Without the decorator, you would have to call ``next(t)`` before + ``t.send()`` could be used. + + """ + + @wraps(func) + def wrapper(*args, **kwargs): + gen = func(*args, **kwargs) + next(gen) + return gen + + return wrapper + + +def ilen(iterable): + """Return the number of items in *iterable*. + + >>> ilen(x for x in range(1000000) if x % 3 == 0) + 333334 + + This consumes the iterable, so handle with care. + + """ + # This approach was selected because benchmarks showed it's likely the + # fastest of the known implementations at the time of writing. + # See GitHub tracker: #236, #230. + counter = count() + deque(zip(iterable, counter), maxlen=0) + return next(counter) + + +def iterate(func, start): + """Return ``start``, ``func(start)``, ``func(func(start))``, ... + + >>> from itertools import islice + >>> list(islice(iterate(lambda x: 2*x, 1), 10)) + [1, 2, 4, 8, 16, 32, 64, 128, 256, 512] + + """ + while True: + yield start + start = func(start) + + +def with_iter(context_manager): + """Wrap an iterable in a ``with`` statement, so it closes once exhausted. + + For example, this will close the file when the iterator is exhausted:: + + upper_lines = (line.upper() for line in with_iter(open('foo'))) + + Any context manager which returns an iterable is a candidate for + ``with_iter``. + + """ + with context_manager as iterable: + yield from iterable + + +def one(iterable, too_short=None, too_long=None): + """Return the first item from *iterable*, which is expected to contain only + that item. Raise an exception if *iterable* is empty or has more than one + item. + + :func:`one` is useful for ensuring that an iterable contains only one item. + For example, it can be used to retrieve the result of a database query + that is expected to return a single row. + + If *iterable* is empty, ``ValueError`` will be raised. You may specify a + different exception with the *too_short* keyword: + + >>> it = [] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too many items in iterable (expected 1)' + >>> too_short = IndexError('too few items') + >>> one(it, too_short=too_short) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + IndexError: too few items + + Similarly, if *iterable* contains more than one item, ``ValueError`` will + be raised. You may specify a different exception with the *too_long* + keyword: + + >>> it = ['too', 'many'] + >>> one(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 'too', + 'many', and perhaps more. + >>> too_long = RuntimeError + >>> one(it, too_long=too_long) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + RuntimeError + + Note that :func:`one` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check iterable + contents less destructively. + + """ + it = iter(iterable) + + try: + first_value = next(it) + except StopIteration as e: + raise ( + too_short or ValueError('too few items in iterable (expected 1)') + ) from e + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +def raise_(exception, *args): + raise exception(*args) + + +def strictly_n(iterable, n, too_short=None, too_long=None): + """Validate that *iterable* has exactly *n* items and return them if + it does. If it has fewer than *n* items, call function *too_short* + with those items. If it has more than *n* items, call function + *too_long* with the first ``n + 1`` items. + + >>> iterable = ['a', 'b', 'c', 'd'] + >>> n = 4 + >>> list(strictly_n(iterable, n)) + ['a', 'b', 'c', 'd'] + + By default, *too_short* and *too_long* are functions that raise + ``ValueError``. + + >>> list(strictly_n('ab', 3)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too few items in iterable (got 2) + + >>> list(strictly_n('abc', 2)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: too many items in iterable (got at least 3) + + You can instead supply functions that do something else. + *too_short* will be called with the number of items in *iterable*. + *too_long* will be called with `n + 1`. + + >>> def too_short(item_count): + ... raise RuntimeError + >>> it = strictly_n('abcd', 6, too_short=too_short) + >>> list(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + RuntimeError + + >>> def too_long(item_count): + ... print('The boss is going to hear about this') + >>> it = strictly_n('abcdef', 4, too_long=too_long) + >>> list(it) + The boss is going to hear about this + ['a', 'b', 'c', 'd'] + + """ + if too_short is None: + too_short = lambda item_count: raise_( + ValueError, + 'Too few items in iterable (got {})'.format(item_count), + ) + + if too_long is None: + too_long = lambda item_count: raise_( + ValueError, + 'Too many items in iterable (got at least {})'.format(item_count), + ) + + it = iter(iterable) + for i in range(n): + try: + item = next(it) + except StopIteration: + too_short(i) + return + else: + yield item + + try: + next(it) + except StopIteration: + pass + else: + too_long(n + 1) + + +def distinct_permutations(iterable, r=None): + """Yield successive distinct permutations of the elements in *iterable*. + + >>> sorted(distinct_permutations([1, 0, 1])) + [(0, 1, 1), (1, 0, 1), (1, 1, 0)] + + Equivalent to ``set(permutations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + Duplicate permutations arise when there are duplicated elements in the + input iterable. The number of items returned is + `n! / (x_1! * x_2! * ... * x_n!)`, where `n` is the total number of + items input, and each `x_i` is the count of a distinct item in the input + sequence. + + If *r* is given, only the *r*-length permutations are yielded. + + >>> sorted(distinct_permutations([1, 0, 1], r=2)) + [(0, 1), (1, 0), (1, 1)] + >>> sorted(distinct_permutations(range(3), r=2)) + [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] + + """ + # Algorithm: https://w.wiki/Qai + def _full(A): + while True: + # Yield the permutation we have + yield tuple(A) + + # Find the largest index i such that A[i] < A[i + 1] + for i in range(size - 2, -1, -1): + if A[i] < A[i + 1]: + break + # If no such index exists, this permutation is the last one + else: + return + + # Find the largest index j greater than j such that A[i] < A[j] + for j in range(size - 1, i, -1): + if A[i] < A[j]: + break + + # Swap the value of A[i] with that of A[j], then reverse the + # sequence from A[i + 1] to form the new permutation + A[i], A[j] = A[j], A[i] + A[i + 1 :] = A[: i - size : -1] # A[i + 1:][::-1] + + # Algorithm: modified from the above + def _partial(A, r): + # Split A into the first r items and the last r items + head, tail = A[:r], A[r:] + right_head_indexes = range(r - 1, -1, -1) + left_tail_indexes = range(len(tail)) + + while True: + # Yield the permutation we have + yield tuple(head) + + # Starting from the right, find the first index of the head with + # value smaller than the maximum value of the tail - call it i. + pivot = tail[-1] + for i in right_head_indexes: + if head[i] < pivot: + break + pivot = head[i] + else: + return + + # Starting from the left, find the first value of the tail + # with a value greater than head[i] and swap. + for j in left_tail_indexes: + if tail[j] > head[i]: + head[i], tail[j] = tail[j], head[i] + break + # If we didn't find one, start from the right and find the first + # index of the head with a value greater than head[i] and swap. + else: + for j in right_head_indexes: + if head[j] > head[i]: + head[i], head[j] = head[j], head[i] + break + + # Reverse head[i + 1:] and swap it with tail[:r - (i + 1)] + tail += head[: i - r : -1] # head[i + 1:][::-1] + i += 1 + head[i:], tail[:] = tail[: r - i], tail[r - i :] + + items = sorted(iterable) + + size = len(items) + if r is None: + r = size + + if 0 < r <= size: + return _full(items) if (r == size) else _partial(items, r) + + return iter(() if r else ((),)) + + +def intersperse(e, iterable, n=1): + """Intersperse filler element *e* among the items in *iterable*, leaving + *n* items between each filler element. + + >>> list(intersperse('!', [1, 2, 3, 4, 5])) + [1, '!', 2, '!', 3, '!', 4, '!', 5] + + >>> list(intersperse(None, [1, 2, 3, 4, 5], n=2)) + [1, 2, None, 3, 4, None, 5] + + """ + if n == 0: + raise ValueError('n must be > 0') + elif n == 1: + # interleave(repeat(e), iterable) -> e, x_0, e, x_1, e, x_2... + # islice(..., 1, None) -> x_0, e, x_1, e, x_2... + return islice(interleave(repeat(e), iterable), 1, None) + else: + # interleave(filler, chunks) -> [e], [x_0, x_1], [e], [x_2, x_3]... + # islice(..., 1, None) -> [x_0, x_1], [e], [x_2, x_3]... + # flatten(...) -> x_0, x_1, e, x_2, x_3... + filler = repeat([e]) + chunks = chunked(iterable, n) + return flatten(islice(interleave(filler, chunks), 1, None)) + + +def unique_to_each(*iterables): + """Return the elements from each of the input iterables that aren't in the + other input iterables. + + For example, suppose you have a set of packages, each with a set of + dependencies:: + + {'pkg_1': {'A', 'B'}, 'pkg_2': {'B', 'C'}, 'pkg_3': {'B', 'D'}} + + If you remove one package, which dependencies can also be removed? + + If ``pkg_1`` is removed, then ``A`` is no longer necessary - it is not + associated with ``pkg_2`` or ``pkg_3``. Similarly, ``C`` is only needed for + ``pkg_2``, and ``D`` is only needed for ``pkg_3``:: + + >>> unique_to_each({'A', 'B'}, {'B', 'C'}, {'B', 'D'}) + [['A'], ['C'], ['D']] + + If there are duplicates in one input iterable that aren't in the others + they will be duplicated in the output. Input order is preserved:: + + >>> unique_to_each("mississippi", "missouri") + [['p', 'p'], ['o', 'u', 'r']] + + It is assumed that the elements of each iterable are hashable. + + """ + pool = [list(it) for it in iterables] + counts = Counter(chain.from_iterable(map(set, pool))) + uniques = {element for element in counts if counts[element] == 1} + return [list(filter(uniques.__contains__, it)) for it in pool] + + +def windowed(seq, n, fillvalue=None, step=1): + """Return a sliding window of width *n* over the given iterable. + + >>> all_windows = windowed([1, 2, 3, 4, 5], 3) + >>> list(all_windows) + [(1, 2, 3), (2, 3, 4), (3, 4, 5)] + + When the window is larger than the iterable, *fillvalue* is used in place + of missing values: + + >>> list(windowed([1, 2, 3], 4)) + [(1, 2, 3, None)] + + Each window will advance in increments of *step*: + + >>> list(windowed([1, 2, 3, 4, 5, 6], 3, fillvalue='!', step=2)) + [(1, 2, 3), (3, 4, 5), (5, 6, '!')] + + To slide into the iterable's items, use :func:`chain` to add filler items + to the left: + + >>> iterable = [1, 2, 3, 4] + >>> n = 3 + >>> padding = [None] * (n - 1) + >>> list(windowed(chain(padding, iterable), 3)) + [(None, None, 1), (None, 1, 2), (1, 2, 3), (2, 3, 4)] + """ + if n < 0: + raise ValueError('n must be >= 0') + if n == 0: + yield tuple() + return + if step < 1: + raise ValueError('step must be >= 1') + + window = deque(maxlen=n) + i = n + for _ in map(window.append, seq): + i -= 1 + if not i: + i = step + yield tuple(window) + + size = len(window) + if size == 0: + return + elif size < n: + yield tuple(chain(window, repeat(fillvalue, n - size))) + elif 0 < i < min(step, n): + window += (fillvalue,) * i + yield tuple(window) + + +def substrings(iterable): + """Yield all of the substrings of *iterable*. + + >>> [''.join(s) for s in substrings('more')] + ['m', 'o', 'r', 'e', 'mo', 'or', 're', 'mor', 'ore', 'more'] + + Note that non-string iterables can also be subdivided. + + >>> list(substrings([0, 1, 2])) + [(0,), (1,), (2,), (0, 1), (1, 2), (0, 1, 2)] + + """ + # The length-1 substrings + seq = [] + for item in iter(iterable): + seq.append(item) + yield (item,) + seq = tuple(seq) + item_count = len(seq) + + # And the rest + for n in range(2, item_count + 1): + for i in range(item_count - n + 1): + yield seq[i : i + n] + + +def substrings_indexes(seq, reverse=False): + """Yield all substrings and their positions in *seq* + + The items yielded will be a tuple of the form ``(substr, i, j)``, where + ``substr == seq[i:j]``. + + This function only works for iterables that support slicing, such as + ``str`` objects. + + >>> for item in substrings_indexes('more'): + ... print(item) + ('m', 0, 1) + ('o', 1, 2) + ('r', 2, 3) + ('e', 3, 4) + ('mo', 0, 2) + ('or', 1, 3) + ('re', 2, 4) + ('mor', 0, 3) + ('ore', 1, 4) + ('more', 0, 4) + + Set *reverse* to ``True`` to yield the same items in the opposite order. + + + """ + r = range(1, len(seq) + 1) + if reverse: + r = reversed(r) + return ( + (seq[i : i + L], i, i + L) for L in r for i in range(len(seq) - L + 1) + ) + + +class bucket: + """Wrap *iterable* and return an object that buckets it iterable into + child iterables based on a *key* function. + + >>> iterable = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'b3'] + >>> s = bucket(iterable, key=lambda x: x[0]) # Bucket by 1st character + >>> sorted(list(s)) # Get the keys + ['a', 'b', 'c'] + >>> a_iterable = s['a'] + >>> next(a_iterable) + 'a1' + >>> next(a_iterable) + 'a2' + >>> list(s['b']) + ['b1', 'b2', 'b3'] + + The original iterable will be advanced and its items will be cached until + they are used by the child iterables. This may require significant storage. + + By default, attempting to select a bucket to which no items belong will + exhaust the iterable and cache all values. + If you specify a *validator* function, selected buckets will instead be + checked against it. + + >>> from itertools import count + >>> it = count(1, 2) # Infinite sequence of odd numbers + >>> key = lambda x: x % 10 # Bucket by last digit + >>> validator = lambda x: x in {1, 3, 5, 7, 9} # Odd digits only + >>> s = bucket(it, key=key, validator=validator) + >>> 2 in s + False + >>> list(s[2]) + [] + + """ + + def __init__(self, iterable, key, validator=None): + self._it = iter(iterable) + self._key = key + self._cache = defaultdict(deque) + self._validator = validator or (lambda x: True) + + def __contains__(self, value): + if not self._validator(value): + return False + + try: + item = next(self[value]) + except StopIteration: + return False + else: + self._cache[value].appendleft(item) + + return True + + def _get_values(self, value): + """ + Helper to yield items from the parent iterator that match *value*. + Items that don't match are stored in the local cache as they + are encountered. + """ + while True: + # If we've cached some items that match the target value, emit + # the first one and evict it from the cache. + if self._cache[value]: + yield self._cache[value].popleft() + # Otherwise we need to advance the parent iterator to search for + # a matching item, caching the rest. + else: + while True: + try: + item = next(self._it) + except StopIteration: + return + item_value = self._key(item) + if item_value == value: + yield item + break + elif self._validator(item_value): + self._cache[item_value].append(item) + + def __iter__(self): + for item in self._it: + item_value = self._key(item) + if self._validator(item_value): + self._cache[item_value].append(item) + + yield from self._cache.keys() + + def __getitem__(self, value): + if not self._validator(value): + return iter(()) + + return self._get_values(value) + + +def spy(iterable, n=1): + """Return a 2-tuple with a list containing the first *n* elements of + *iterable*, and an iterator with the same items as *iterable*. + This allows you to "look ahead" at the items in the iterable without + advancing it. + + There is one item in the list by default: + + >>> iterable = 'abcdefg' + >>> head, iterable = spy(iterable) + >>> head + ['a'] + >>> list(iterable) + ['a', 'b', 'c', 'd', 'e', 'f', 'g'] + + You may use unpacking to retrieve items instead of lists: + + >>> (head,), iterable = spy('abcdefg') + >>> head + 'a' + >>> (first, second), iterable = spy('abcdefg', 2) + >>> first + 'a' + >>> second + 'b' + + The number of items requested can be larger than the number of items in + the iterable: + + >>> iterable = [1, 2, 3, 4, 5] + >>> head, iterable = spy(iterable, 10) + >>> head + [1, 2, 3, 4, 5] + >>> list(iterable) + [1, 2, 3, 4, 5] + + """ + it = iter(iterable) + head = take(n, it) + + return head.copy(), chain(head, it) + + +def interleave(*iterables): + """Return a new iterable yielding from each iterable in turn, + until the shortest is exhausted. + + >>> list(interleave([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7] + + For a version that doesn't terminate after the shortest iterable is + exhausted, see :func:`interleave_longest`. + + """ + return chain.from_iterable(zip(*iterables)) + + +def interleave_longest(*iterables): + """Return a new iterable yielding from each iterable in turn, + skipping any that are exhausted. + + >>> list(interleave_longest([1, 2, 3], [4, 5], [6, 7, 8])) + [1, 4, 6, 2, 5, 7, 3, 8] + + This function produces the same output as :func:`roundrobin`, but may + perform better for some inputs (in particular when the number of iterables + is large). + + """ + i = chain.from_iterable(zip_longest(*iterables, fillvalue=_marker)) + return (x for x in i if x is not _marker) + + +def interleave_evenly(iterables, lengths=None): + """ + Interleave multiple iterables so that their elements are evenly distributed + throughout the output sequence. + + >>> iterables = [1, 2, 3, 4, 5], ['a', 'b'] + >>> list(interleave_evenly(iterables)) + [1, 2, 'a', 3, 4, 'b', 5] + + >>> iterables = [[1, 2, 3], [4, 5], [6, 7, 8]] + >>> list(interleave_evenly(iterables)) + [1, 6, 4, 2, 7, 3, 8, 5] + + This function requires iterables of known length. Iterables without + ``__len__()`` can be used by manually specifying lengths with *lengths*: + + >>> from itertools import combinations, repeat + >>> iterables = [combinations(range(4), 2), ['a', 'b', 'c']] + >>> lengths = [4 * (4 - 1) // 2, 3] + >>> list(interleave_evenly(iterables, lengths=lengths)) + [(0, 1), (0, 2), 'a', (0, 3), (1, 2), 'b', (1, 3), (2, 3), 'c'] + + Based on Bresenham's algorithm. + """ + if lengths is None: + try: + lengths = [len(it) for it in iterables] + except TypeError: + raise ValueError( + 'Iterable lengths could not be determined automatically. ' + 'Specify them with the lengths keyword.' + ) + elif len(iterables) != len(lengths): + raise ValueError('Mismatching number of iterables and lengths.') + + dims = len(lengths) + + # sort iterables by length, descending + lengths_permute = sorted( + range(dims), key=lambda i: lengths[i], reverse=True + ) + lengths_desc = [lengths[i] for i in lengths_permute] + iters_desc = [iter(iterables[i]) for i in lengths_permute] + + # the longest iterable is the primary one (Bresenham: the longest + # distance along an axis) + delta_primary, deltas_secondary = lengths_desc[0], lengths_desc[1:] + iter_primary, iters_secondary = iters_desc[0], iters_desc[1:] + errors = [delta_primary // dims] * len(deltas_secondary) + + to_yield = sum(lengths) + while to_yield: + yield next(iter_primary) + to_yield -= 1 + # update errors for each secondary iterable + errors = [e - delta for e, delta in zip(errors, deltas_secondary)] + + # those iterables for which the error is negative are yielded + # ("diagonal step" in Bresenham) + for i, e in enumerate(errors): + if e < 0: + yield next(iters_secondary[i]) + to_yield -= 1 + errors[i] += delta_primary + + +def collapse(iterable, base_type=None, levels=None): + """Flatten an iterable with multiple levels of nesting (e.g., a list of + lists of tuples) into non-iterable types. + + >>> iterable = [(1, 2), ([3, 4], [[5], [6]])] + >>> list(collapse(iterable)) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and + will not be collapsed. + + To avoid collapsing other types, specify *base_type*: + + >>> iterable = ['ab', ('cd', 'ef'), ['gh', 'ij']] + >>> list(collapse(iterable, base_type=tuple)) + ['ab', ('cd', 'ef'), 'gh', 'ij'] + + Specify *levels* to stop flattening after a certain level: + + >>> iterable = [('a', ['b']), ('c', ['d'])] + >>> list(collapse(iterable)) # Fully flattened + ['a', 'b', 'c', 'd'] + >>> list(collapse(iterable, levels=1)) # Only one level flattened + ['a', ['b'], 'c', ['d']] + + """ + + def walk(node, level): + if ( + ((levels is not None) and (level > levels)) + or isinstance(node, (str, bytes)) + or ((base_type is not None) and isinstance(node, base_type)) + ): + yield node + return + + try: + tree = iter(node) + except TypeError: + yield node + return + else: + for child in tree: + yield from walk(child, level + 1) + + yield from walk(iterable, 0) + + +def side_effect(func, iterable, chunk_size=None, before=None, after=None): + """Invoke *func* on each item in *iterable* (or on each *chunk_size* group + of items) before yielding the item. + + `func` must be a function that takes a single argument. Its return value + will be discarded. + + *before* and *after* are optional functions that take no arguments. They + will be executed before iteration starts and after it ends, respectively. + + `side_effect` can be used for logging, updating progress bars, or anything + that is not functionally "pure." + + Emitting a status message: + + >>> from more_itertools import consume + >>> func = lambda item: print('Received {}'.format(item)) + >>> consume(side_effect(func, range(2))) + Received 0 + Received 1 + + Operating on chunks of items: + + >>> pair_sums = [] + >>> func = lambda chunk: pair_sums.append(sum(chunk)) + >>> list(side_effect(func, [0, 1, 2, 3, 4, 5], 2)) + [0, 1, 2, 3, 4, 5] + >>> list(pair_sums) + [1, 5, 9] + + Writing to a file-like object: + + >>> from io import StringIO + >>> from more_itertools import consume + >>> f = StringIO() + >>> func = lambda x: print(x, file=f) + >>> before = lambda: print(u'HEADER', file=f) + >>> after = f.close + >>> it = [u'a', u'b', u'c'] + >>> consume(side_effect(func, it, before=before, after=after)) + >>> f.closed + True + + """ + try: + if before is not None: + before() + + if chunk_size is None: + for item in iterable: + func(item) + yield item + else: + for chunk in chunked(iterable, chunk_size): + func(chunk) + yield from chunk + finally: + if after is not None: + after() + + +def sliced(seq, n, strict=False): + """Yield slices of length *n* from the sequence *seq*. + + >>> list(sliced((1, 2, 3, 4, 5, 6), 3)) + [(1, 2, 3), (4, 5, 6)] + + By the default, the last yielded slice will have fewer than *n* elements + if the length of *seq* is not divisible by *n*: + + >>> list(sliced((1, 2, 3, 4, 5, 6, 7, 8), 3)) + [(1, 2, 3), (4, 5, 6), (7, 8)] + + If the length of *seq* is not divisible by *n* and *strict* is + ``True``, then ``ValueError`` will be raised before the last + slice is yielded. + + This function will only work for iterables that support slicing. + For non-sliceable iterables, see :func:`chunked`. + + """ + iterator = takewhile(len, (seq[i : i + n] for i in count(0, n))) + if strict: + + def ret(): + for _slice in iterator: + if len(_slice) != n: + raise ValueError("seq is not divisible by n.") + yield _slice + + return iter(ret()) + else: + return iterator + + +def split_at(iterable, pred, maxsplit=-1, keep_separator=False): + """Yield lists of items from *iterable*, where each list is delimited by + an item where callable *pred* returns ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b')) + [['a'], ['c', 'd', 'c'], ['a']] + + >>> list(split_at(range(10), lambda n: n % 2 == 1)) + [[0], [2], [4], [6], [8], []] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_at(range(10), lambda n: n % 2 == 1, maxsplit=2)) + [[0], [2], [4, 5, 6, 7, 8, 9]] + + By default, the delimiting items are not included in the output. + The include them, set *keep_separator* to ``True``. + + >>> list(split_at('abcdcba', lambda x: x == 'b', keep_separator=True)) + [['a'], ['b'], ['c', 'd', 'c'], ['b'], ['a']] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item): + yield buf + if keep_separator: + yield [item] + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + else: + buf.append(item) + yield buf + + +def split_before(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends just before + an item for which callable *pred* returns ``True``: + + >>> list(split_before('OneTwo', lambda s: s.isupper())) + [['O', 'n', 'e'], ['T', 'w', 'o']] + + >>> list(split_before(range(10), lambda n: n % 3 == 0)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_before(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]] + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield [item] + list(it) + return + buf = [] + maxsplit -= 1 + buf.append(item) + if buf: + yield buf + + +def split_after(iterable, pred, maxsplit=-1): + """Yield lists of items from *iterable*, where each list ends with an + item where callable *pred* returns ``True``: + + >>> list(split_after('one1two2', lambda s: s.isdigit())) + [['o', 'n', 'e', '1'], ['t', 'w', 'o', '2']] + + >>> list(split_after(range(10), lambda n: n % 3 == 0)) + [[0], [1, 2, 3], [4, 5, 6], [7, 8, 9]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_after(range(10), lambda n: n % 3 == 0, maxsplit=2)) + [[0], [1, 2, 3], [4, 5, 6, 7, 8, 9]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + buf = [] + it = iter(iterable) + for item in it: + buf.append(item) + if pred(item) and buf: + yield buf + if maxsplit == 1: + yield list(it) + return + buf = [] + maxsplit -= 1 + if buf: + yield buf + + +def split_when(iterable, pred, maxsplit=-1): + """Split *iterable* into pieces based on the output of *pred*. + *pred* should be a function that takes successive pairs of items and + returns ``True`` if the iterable should be split in between them. + + For example, to find runs of increasing numbers, split the iterable when + element ``i`` is larger than element ``i + 1``: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], lambda x, y: x > y)) + [[1, 2, 3, 3], [2, 5], [2, 4], [2]] + + At most *maxsplit* splits are done. If *maxsplit* is not specified or -1, + then there is no limit on the number of splits: + + >>> list(split_when([1, 2, 3, 3, 2, 5, 2, 4, 2], + ... lambda x, y: x > y, maxsplit=2)) + [[1, 2, 3, 3], [2, 5], [2, 4, 2]] + + """ + if maxsplit == 0: + yield list(iterable) + return + + it = iter(iterable) + try: + cur_item = next(it) + except StopIteration: + return + + buf = [cur_item] + for next_item in it: + if pred(cur_item, next_item): + yield buf + if maxsplit == 1: + yield [next_item] + list(it) + return + buf = [] + maxsplit -= 1 + + buf.append(next_item) + cur_item = next_item + + yield buf + + +def split_into(iterable, sizes): + """Yield a list of sequential items from *iterable* of length 'n' for each + integer 'n' in *sizes*. + + >>> list(split_into([1,2,3,4,5,6], [1,2,3])) + [[1], [2, 3], [4, 5, 6]] + + If the sum of *sizes* is smaller than the length of *iterable*, then the + remaining items of *iterable* will not be returned. + + >>> list(split_into([1,2,3,4,5,6], [2,3])) + [[1, 2], [3, 4, 5]] + + If the sum of *sizes* is larger than the length of *iterable*, fewer items + will be returned in the iteration that overruns *iterable* and further + lists will be empty: + + >>> list(split_into([1,2,3,4], [1,2,3,4])) + [[1], [2, 3], [4], []] + + When a ``None`` object is encountered in *sizes*, the returned list will + contain items up to the end of *iterable* the same way that itertools.slice + does: + + >>> list(split_into([1,2,3,4,5,6,7,8,9,0], [2,3,None])) + [[1, 2], [3, 4, 5], [6, 7, 8, 9, 0]] + + :func:`split_into` can be useful for grouping a series of items where the + sizes of the groups are not uniform. An example would be where in a row + from a table, multiple columns represent elements of the same feature + (e.g. a point represented by x,y,z) but, the format is not the same for + all columns. + """ + # convert the iterable argument into an iterator so its contents can + # be consumed by islice in case it is a generator + it = iter(iterable) + + for size in sizes: + if size is None: + yield list(it) + return + else: + yield list(islice(it, size)) + + +def padded(iterable, fillvalue=None, n=None, next_multiple=False): + """Yield the elements from *iterable*, followed by *fillvalue*, such that + at least *n* items are emitted. + + >>> list(padded([1, 2, 3], '?', 5)) + [1, 2, 3, '?', '?'] + + If *next_multiple* is ``True``, *fillvalue* will be emitted until the + number of items emitted is a multiple of *n*:: + + >>> list(padded([1, 2, 3, 4], n=3, next_multiple=True)) + [1, 2, 3, 4, None, None] + + If *n* is ``None``, *fillvalue* will be emitted indefinitely. + + """ + it = iter(iterable) + if n is None: + yield from chain(it, repeat(fillvalue)) + elif n < 1: + raise ValueError('n must be at least 1') + else: + item_count = 0 + for item in it: + yield item + item_count += 1 + + remaining = (n - item_count) % n if next_multiple else n - item_count + for _ in range(remaining): + yield fillvalue + + +def repeat_each(iterable, n=2): + """Repeat each element in *iterable* *n* times. + + >>> list(repeat_each('ABC', 3)) + ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C'] + """ + return chain.from_iterable(map(repeat, iterable, repeat(n))) + + +def repeat_last(iterable, default=None): + """After the *iterable* is exhausted, keep yielding its last element. + + >>> list(islice(repeat_last(range(3)), 5)) + [0, 1, 2, 2, 2] + + If the iterable is empty, yield *default* forever:: + + >>> list(islice(repeat_last(range(0), 42), 5)) + [42, 42, 42, 42, 42] + + """ + item = _marker + for item in iterable: + yield item + final = default if item is _marker else item + yield from repeat(final) + + +def distribute(n, iterable): + """Distribute the items from *iterable* among *n* smaller iterables. + + >>> group_1, group_2 = distribute(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 3, 5] + >>> list(group_2) + [2, 4, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = distribute(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 4, 7], [2, 5], [3, 6]] + + If the length of *iterable* is smaller than *n*, then the last returned + iterables will be empty: + + >>> children = distribute(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function uses :func:`itertools.tee` and may require significant + storage. If you need the order items in the smaller iterables to match the + original iterable, see :func:`divide`. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + children = tee(iterable, n) + return [islice(it, index, None, n) for index, it in enumerate(children)] + + +def stagger(iterable, offsets=(-1, 0, 1), longest=False, fillvalue=None): + """Yield tuples whose elements are offset from *iterable*. + The amount by which the `i`-th item in each tuple is offset is given by + the `i`-th item in *offsets*. + + >>> list(stagger([0, 1, 2, 3])) + [(None, 0, 1), (0, 1, 2), (1, 2, 3)] + >>> list(stagger(range(8), offsets=(0, 2, 4))) + [(0, 2, 4), (1, 3, 5), (2, 4, 6), (3, 5, 7)] + + By default, the sequence will end when the final element of a tuple is the + last item in the iterable. To continue until the first element of a tuple + is the last item in the iterable, set *longest* to ``True``:: + + >>> list(stagger([0, 1, 2, 3], longest=True)) + [(None, 0, 1), (0, 1, 2), (1, 2, 3), (2, 3, None), (3, None, None)] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + children = tee(iterable, len(offsets)) + + return zip_offset( + *children, offsets=offsets, longest=longest, fillvalue=fillvalue + ) + + +def zip_equal(*iterables): + """``zip`` the input *iterables* together, but raise + ``UnequalIterablesError`` if they aren't all the same length. + + >>> it_1 = range(3) + >>> it_2 = iter('abc') + >>> list(zip_equal(it_1, it_2)) + [(0, 'a'), (1, 'b'), (2, 'c')] + + >>> it_1 = range(3) + >>> it_2 = iter('abcd') + >>> list(zip_equal(it_1, it_2)) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + more_itertools.more.UnequalIterablesError: Iterables have different + lengths + + """ + if hexversion >= 0x30A00A6: + warnings.warn( + ( + 'zip_equal will be removed in a future version of ' + 'more-itertools. Use the builtin zip function with ' + 'strict=True instead.' + ), + DeprecationWarning, + ) + + return _zip_equal(*iterables) + + +def zip_offset(*iterables, offsets, longest=False, fillvalue=None): + """``zip`` the input *iterables* together, but offset the `i`-th iterable + by the `i`-th item in *offsets*. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1))) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e')] + + This can be used as a lightweight alternative to SciPy or pandas to analyze + data sets in which some series have a lead or lag relationship. + + By default, the sequence will end when the shortest iterable is exhausted. + To continue until the longest iterable is exhausted, set *longest* to + ``True``. + + >>> list(zip_offset('0123', 'abcdef', offsets=(0, 1), longest=True)) + [('0', 'b'), ('1', 'c'), ('2', 'd'), ('3', 'e'), (None, 'f')] + + By default, ``None`` will be used to replace offsets beyond the end of the + sequence. Specify *fillvalue* to use some other value. + + """ + if len(iterables) != len(offsets): + raise ValueError("Number of iterables and offsets didn't match") + + staggered = [] + for it, n in zip(iterables, offsets): + if n < 0: + staggered.append(chain(repeat(fillvalue, -n), it)) + elif n > 0: + staggered.append(islice(it, n, None)) + else: + staggered.append(it) + + if longest: + return zip_longest(*staggered, fillvalue=fillvalue) + + return zip(*staggered) + + +def sort_together(iterables, key_list=(0,), key=None, reverse=False): + """Return the input iterables sorted together, with *key_list* as the + priority for sorting. All iterables are trimmed to the length of the + shortest one. + + This can be used like the sorting function in a spreadsheet. If each + iterable represents a column of data, the key list determines which + columns are used for sorting. + + By default, all iterables are sorted using the ``0``-th iterable:: + + >>> iterables = [(4, 3, 2, 1), ('a', 'b', 'c', 'd')] + >>> sort_together(iterables) + [(1, 2, 3, 4), ('d', 'c', 'b', 'a')] + + Set a different key list to sort according to another iterable. + Specifying multiple keys dictates how ties are broken:: + + >>> iterables = [(3, 1, 2), (0, 1, 0), ('c', 'b', 'a')] + >>> sort_together(iterables, key_list=(1, 2)) + [(2, 3, 1), (0, 0, 1), ('a', 'c', 'b')] + + To sort by a function of the elements of the iterable, pass a *key* + function. Its arguments are the elements of the iterables corresponding to + the key list:: + + >>> names = ('a', 'b', 'c') + >>> lengths = (1, 2, 3) + >>> widths = (5, 2, 1) + >>> def area(length, width): + ... return length * width + >>> sort_together([names, lengths, widths], key_list=(1, 2), key=area) + [('c', 'b', 'a'), (3, 2, 1), (1, 2, 5)] + + Set *reverse* to ``True`` to sort in descending order. + + >>> sort_together([(1, 2, 3), ('c', 'b', 'a')], reverse=True) + [(3, 2, 1), ('a', 'b', 'c')] + + """ + if key is None: + # if there is no key function, the key argument to sorted is an + # itemgetter + key_argument = itemgetter(*key_list) + else: + # if there is a key function, call it with the items at the offsets + # specified by the key function as arguments + key_list = list(key_list) + if len(key_list) == 1: + # if key_list contains a single item, pass the item at that offset + # as the only argument to the key function + key_offset = key_list[0] + key_argument = lambda zipped_items: key(zipped_items[key_offset]) + else: + # if key_list contains multiple items, use itemgetter to return a + # tuple of items, which we pass as *args to the key function + get_key_items = itemgetter(*key_list) + key_argument = lambda zipped_items: key( + *get_key_items(zipped_items) + ) + + return list( + zip(*sorted(zip(*iterables), key=key_argument, reverse=reverse)) + ) + + +def unzip(iterable): + """The inverse of :func:`zip`, this function disaggregates the elements + of the zipped *iterable*. + + The ``i``-th iterable contains the ``i``-th element from each element + of the zipped iterable. The first element is used to determine the + length of the remaining elements. + + >>> iterable = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> letters, numbers = unzip(iterable) + >>> list(letters) + ['a', 'b', 'c', 'd'] + >>> list(numbers) + [1, 2, 3, 4] + + This is similar to using ``zip(*iterable)``, but it avoids reading + *iterable* into memory. Note, however, that this function uses + :func:`itertools.tee` and thus may require significant storage. + + """ + head, iterable = spy(iter(iterable)) + if not head: + # empty iterable, e.g. zip([], [], []) + return () + # spy returns a one-length iterable as head + head = head[0] + iterables = tee(iterable, len(head)) + + def itemgetter(i): + def getter(obj): + try: + return obj[i] + except IndexError: + # basically if we have an iterable like + # iter([(1, 2, 3), (4, 5), (6,)]) + # the second unzipped iterable would fail at the third tuple + # since it would try to access tup[1] + # same with the third unzipped iterable and the second tuple + # to support these "improperly zipped" iterables, + # we create a custom itemgetter + # which just stops the unzipped iterables + # at first length mismatch + raise StopIteration + + return getter + + return tuple(map(itemgetter(i), it) for i, it in enumerate(iterables)) + + +def divide(n, iterable): + """Divide the elements from *iterable* into *n* parts, maintaining + order. + + >>> group_1, group_2 = divide(2, [1, 2, 3, 4, 5, 6]) + >>> list(group_1) + [1, 2, 3] + >>> list(group_2) + [4, 5, 6] + + If the length of *iterable* is not evenly divisible by *n*, then the + length of the returned iterables will not be identical: + + >>> children = divide(3, [1, 2, 3, 4, 5, 6, 7]) + >>> [list(c) for c in children] + [[1, 2, 3], [4, 5], [6, 7]] + + If the length of the iterable is smaller than n, then the last returned + iterables will be empty: + + >>> children = divide(5, [1, 2, 3]) + >>> [list(c) for c in children] + [[1], [2], [3], [], []] + + This function will exhaust the iterable before returning and may require + significant storage. If order is not important, see :func:`distribute`, + which does not first pull the iterable into memory. + + """ + if n < 1: + raise ValueError('n must be at least 1') + + try: + iterable[:0] + except TypeError: + seq = tuple(iterable) + else: + seq = iterable + + q, r = divmod(len(seq), n) + + ret = [] + stop = 0 + for i in range(1, n + 1): + start = stop + stop += q + 1 if i <= r else q + ret.append(iter(seq[start:stop])) + + return ret + + +def always_iterable(obj, base_type=(str, bytes)): + """If *obj* is iterable, return an iterator over its items:: + + >>> obj = (1, 2, 3) + >>> list(always_iterable(obj)) + [1, 2, 3] + + If *obj* is not iterable, return a one-item iterable containing *obj*:: + + >>> obj = 1 + >>> list(always_iterable(obj)) + [1] + + If *obj* is ``None``, return an empty iterable: + + >>> obj = None + >>> list(always_iterable(None)) + [] + + By default, binary and text strings are not considered iterable:: + + >>> obj = 'foo' + >>> list(always_iterable(obj)) + ['foo'] + + If *base_type* is set, objects for which ``isinstance(obj, base_type)`` + returns ``True`` won't be considered iterable. + + >>> obj = {'a': 1} + >>> list(always_iterable(obj)) # Iterate over the dict's keys + ['a'] + >>> list(always_iterable(obj, base_type=dict)) # Treat dicts as a unit + [{'a': 1}] + + Set *base_type* to ``None`` to avoid any special handling and treat objects + Python considers iterable as iterable: + + >>> obj = 'foo' + >>> list(always_iterable(obj, base_type=None)) + ['f', 'o', 'o'] + """ + if obj is None: + return iter(()) + + if (base_type is not None) and isinstance(obj, base_type): + return iter((obj,)) + + try: + return iter(obj) + except TypeError: + return iter((obj,)) + + +def adjacent(predicate, iterable, distance=1): + """Return an iterable over `(bool, item)` tuples where the `item` is + drawn from *iterable* and the `bool` indicates whether + that item satisfies the *predicate* or is adjacent to an item that does. + + For example, to find whether items are adjacent to a ``3``:: + + >>> list(adjacent(lambda x: x == 3, range(6))) + [(False, 0), (False, 1), (True, 2), (True, 3), (True, 4), (False, 5)] + + Set *distance* to change what counts as adjacent. For example, to find + whether items are two places away from a ``3``: + + >>> list(adjacent(lambda x: x == 3, range(6), distance=2)) + [(False, 0), (True, 1), (True, 2), (True, 3), (True, 4), (True, 5)] + + This is useful for contextualizing the results of a search function. + For example, a code comparison tool might want to identify lines that + have changed, but also surrounding lines to give the viewer of the diff + context. + + The predicate function will only be called once for each item in the + iterable. + + See also :func:`groupby_transform`, which can be used with this function + to group ranges of items with the same `bool` value. + + """ + # Allow distance=0 mainly for testing that it reproduces results with map() + if distance < 0: + raise ValueError('distance must be at least 0') + + i1, i2 = tee(iterable) + padding = [False] * distance + selected = chain(padding, map(predicate, i1), padding) + adjacent_to_selected = map(any, windowed(selected, 2 * distance + 1)) + return zip(adjacent_to_selected, i2) + + +def groupby_transform(iterable, keyfunc=None, valuefunc=None, reducefunc=None): + """An extension of :func:`itertools.groupby` that can apply transformations + to the grouped data. + + * *keyfunc* is a function computing a key value for each item in *iterable* + * *valuefunc* is a function that transforms the individual items from + *iterable* after grouping + * *reducefunc* is a function that transforms each group of items + + >>> iterable = 'aAAbBBcCC' + >>> keyfunc = lambda k: k.upper() + >>> valuefunc = lambda v: v.lower() + >>> reducefunc = lambda g: ''.join(g) + >>> list(groupby_transform(iterable, keyfunc, valuefunc, reducefunc)) + [('A', 'aaa'), ('B', 'bbb'), ('C', 'ccc')] + + Each optional argument defaults to an identity function if not specified. + + :func:`groupby_transform` is useful when grouping elements of an iterable + using a separate iterable as the key. To do this, :func:`zip` the iterables + and pass a *keyfunc* that extracts the first element and a *valuefunc* + that extracts the second element:: + + >>> from operator import itemgetter + >>> keys = [0, 0, 1, 1, 1, 2, 2, 2, 3] + >>> values = 'abcdefghi' + >>> iterable = zip(keys, values) + >>> grouper = groupby_transform(iterable, itemgetter(0), itemgetter(1)) + >>> [(k, ''.join(g)) for k, g in grouper] + [(0, 'ab'), (1, 'cde'), (2, 'fgh'), (3, 'i')] + + Note that the order of items in the iterable is significant. + Only adjacent items are grouped together, so if you don't want any + duplicate groups, you should sort the iterable by the key function. + + """ + ret = groupby(iterable, keyfunc) + if valuefunc: + ret = ((k, map(valuefunc, g)) for k, g in ret) + if reducefunc: + ret = ((k, reducefunc(g)) for k, g in ret) + + return ret + + +class numeric_range(abc.Sequence, abc.Hashable): + """An extension of the built-in ``range()`` function whose arguments can + be any orderable numeric type. + + With only *stop* specified, *start* defaults to ``0`` and *step* + defaults to ``1``. The output items will match the type of *stop*: + + >>> list(numeric_range(3.5)) + [0.0, 1.0, 2.0, 3.0] + + With only *start* and *stop* specified, *step* defaults to ``1``. The + output items will match the type of *start*: + + >>> from decimal import Decimal + >>> start = Decimal('2.1') + >>> stop = Decimal('5.1') + >>> list(numeric_range(start, stop)) + [Decimal('2.1'), Decimal('3.1'), Decimal('4.1')] + + With *start*, *stop*, and *step* specified the output items will match + the type of ``start + step``: + + >>> from fractions import Fraction + >>> start = Fraction(1, 2) # Start at 1/2 + >>> stop = Fraction(5, 2) # End at 5/2 + >>> step = Fraction(1, 2) # Count by 1/2 + >>> list(numeric_range(start, stop, step)) + [Fraction(1, 2), Fraction(1, 1), Fraction(3, 2), Fraction(2, 1)] + + If *step* is zero, ``ValueError`` is raised. Negative steps are supported: + + >>> list(numeric_range(3, -1, -1.0)) + [3.0, 2.0, 1.0, 0.0] + + Be aware of the limitations of floating point numbers; the representation + of the yielded numbers may be surprising. + + ``datetime.datetime`` objects can be used for *start* and *stop*, if *step* + is a ``datetime.timedelta`` object: + + >>> import datetime + >>> start = datetime.datetime(2019, 1, 1) + >>> stop = datetime.datetime(2019, 1, 3) + >>> step = datetime.timedelta(days=1) + >>> items = iter(numeric_range(start, stop, step)) + >>> next(items) + datetime.datetime(2019, 1, 1, 0, 0) + >>> next(items) + datetime.datetime(2019, 1, 2, 0, 0) + + """ + + _EMPTY_HASH = hash(range(0, 0)) + + def __init__(self, *args): + argc = len(args) + if argc == 1: + (self._stop,) = args + self._start = type(self._stop)(0) + self._step = type(self._stop - self._start)(1) + elif argc == 2: + self._start, self._stop = args + self._step = type(self._stop - self._start)(1) + elif argc == 3: + self._start, self._stop, self._step = args + elif argc == 0: + raise TypeError( + 'numeric_range expected at least ' + '1 argument, got {}'.format(argc) + ) + else: + raise TypeError( + 'numeric_range expected at most ' + '3 arguments, got {}'.format(argc) + ) + + self._zero = type(self._step)(0) + if self._step == self._zero: + raise ValueError('numeric_range() arg 3 must not be zero') + self._growing = self._step > self._zero + self._init_len() + + def __bool__(self): + if self._growing: + return self._start < self._stop + else: + return self._start > self._stop + + def __contains__(self, elem): + if self._growing: + if self._start <= elem < self._stop: + return (elem - self._start) % self._step == self._zero + else: + if self._start >= elem > self._stop: + return (self._start - elem) % (-self._step) == self._zero + + return False + + def __eq__(self, other): + if isinstance(other, numeric_range): + empty_self = not bool(self) + empty_other = not bool(other) + if empty_self or empty_other: + return empty_self and empty_other # True if both empty + else: + return ( + self._start == other._start + and self._step == other._step + and self._get_by_index(-1) == other._get_by_index(-1) + ) + else: + return False + + def __getitem__(self, key): + if isinstance(key, int): + return self._get_by_index(key) + elif isinstance(key, slice): + step = self._step if key.step is None else key.step * self._step + + if key.start is None or key.start <= -self._len: + start = self._start + elif key.start >= self._len: + start = self._stop + else: # -self._len < key.start < self._len + start = self._get_by_index(key.start) + + if key.stop is None or key.stop >= self._len: + stop = self._stop + elif key.stop <= -self._len: + stop = self._start + else: # -self._len < key.stop < self._len + stop = self._get_by_index(key.stop) + + return numeric_range(start, stop, step) + else: + raise TypeError( + 'numeric range indices must be ' + 'integers or slices, not {}'.format(type(key).__name__) + ) + + def __hash__(self): + if self: + return hash((self._start, self._get_by_index(-1), self._step)) + else: + return self._EMPTY_HASH + + def __iter__(self): + values = (self._start + (n * self._step) for n in count()) + if self._growing: + return takewhile(partial(gt, self._stop), values) + else: + return takewhile(partial(lt, self._stop), values) + + def __len__(self): + return self._len + + def _init_len(self): + if self._growing: + start = self._start + stop = self._stop + step = self._step + else: + start = self._stop + stop = self._start + step = -self._step + distance = stop - start + if distance <= self._zero: + self._len = 0 + else: # distance > 0 and step > 0: regular euclidean division + q, r = divmod(distance, step) + self._len = int(q) + int(r != self._zero) + + def __reduce__(self): + return numeric_range, (self._start, self._stop, self._step) + + def __repr__(self): + if self._step == 1: + return "numeric_range({}, {})".format( + repr(self._start), repr(self._stop) + ) + else: + return "numeric_range({}, {}, {})".format( + repr(self._start), repr(self._stop), repr(self._step) + ) + + def __reversed__(self): + return iter( + numeric_range( + self._get_by_index(-1), self._start - self._step, -self._step + ) + ) + + def count(self, value): + return int(value in self) + + def index(self, value): + if self._growing: + if self._start <= value < self._stop: + q, r = divmod(value - self._start, self._step) + if r == self._zero: + return int(q) + else: + if self._start >= value > self._stop: + q, r = divmod(self._start - value, -self._step) + if r == self._zero: + return int(q) + + raise ValueError("{} is not in numeric range".format(value)) + + def _get_by_index(self, i): + if i < 0: + i += self._len + if i < 0 or i >= self._len: + raise IndexError("numeric range object index out of range") + return self._start + i * self._step + + +def count_cycle(iterable, n=None): + """Cycle through the items from *iterable* up to *n* times, yielding + the number of completed cycles along with each item. If *n* is omitted the + process repeats indefinitely. + + >>> list(count_cycle('AB', 3)) + [(0, 'A'), (0, 'B'), (1, 'A'), (1, 'B'), (2, 'A'), (2, 'B')] + + """ + iterable = tuple(iterable) + if not iterable: + return iter(()) + counter = count() if n is None else range(n) + return ((i, item) for i in counter for item in iterable) + + +def mark_ends(iterable): + """Yield 3-tuples of the form ``(is_first, is_last, item)``. + + >>> list(mark_ends('ABC')) + [(True, False, 'A'), (False, False, 'B'), (False, True, 'C')] + + Use this when looping over an iterable to take special action on its first + and/or last items: + + >>> iterable = ['Header', 100, 200, 'Footer'] + >>> total = 0 + >>> for is_first, is_last, item in mark_ends(iterable): + ... if is_first: + ... continue # Skip the header + ... if is_last: + ... continue # Skip the footer + ... total += item + >>> print(total) + 300 + """ + it = iter(iterable) + + try: + b = next(it) + except StopIteration: + return + + try: + for i in count(): + a = b + b = next(it) + yield i == 0, False, a + + except StopIteration: + yield i == 0, True, a + + +def locate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(locate([0, 1, 1, 0, 1, 0, 0])) + [1, 2, 4] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item. + + >>> list(locate(['a', 'b', 'c', 'b'], lambda x: x == 'b')) + [1, 3] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(locate(iterable, pred=pred, window_size=3)) + [1, 5, 9] + + Use with :func:`seekable` to find indexes and then retrieve the associated + items: + + >>> from itertools import count + >>> from more_itertools import seekable + >>> source = (3 * n + 1 if (n % 2) else n // 2 for n in count()) + >>> it = seekable(source) + >>> pred = lambda x: x > 100 + >>> indexes = locate(it, pred=pred) + >>> i = next(indexes) + >>> it.seek(i) + >>> next(it) + 106 + + """ + if window_size is None: + return compress(count(), map(pred, iterable)) + + if window_size < 1: + raise ValueError('window size must be at least 1') + + it = windowed(iterable, window_size, fillvalue=_marker) + return compress(count(), starmap(pred, it)) + + +def longest_common_prefix(iterables): + """Yield elements of the longest common prefix amongst given *iterables*. + + >>> ''.join(longest_common_prefix(['abcd', 'abc', 'abf'])) + 'ab' + + """ + return (c[0] for c in takewhile(all_equal, zip(*iterables))) + + +def lstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the beginning + for which *pred* returns ``True``. + + For example, to remove a set of items from the start of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(lstrip(iterable, pred)) + [1, 2, None, 3, False, None] + + This function is analogous to to :func:`str.lstrip`, and is essentially + an wrapper for :func:`itertools.dropwhile`. + + """ + return dropwhile(pred, iterable) + + +def rstrip(iterable, pred): + """Yield the items from *iterable*, but strip any from the end + for which *pred* returns ``True``. + + For example, to remove a set of items from the end of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(rstrip(iterable, pred)) + [None, False, None, 1, 2, None, 3] + + This function is analogous to :func:`str.rstrip`. + + """ + cache = [] + cache_append = cache.append + cache_clear = cache.clear + for x in iterable: + if pred(x): + cache_append(x) + else: + yield from cache + cache_clear() + yield x + + +def strip(iterable, pred): + """Yield the items from *iterable*, but strip any from the + beginning and end for which *pred* returns ``True``. + + For example, to remove a set of items from both ends of an iterable: + + >>> iterable = (None, False, None, 1, 2, None, 3, False, None) + >>> pred = lambda x: x in {None, False, ''} + >>> list(strip(iterable, pred)) + [1, 2, None, 3] + + This function is analogous to :func:`str.strip`. + + """ + return rstrip(lstrip(iterable, pred), pred) + + +class islice_extended: + """An extension of :func:`itertools.islice` that supports negative values + for *stop*, *start*, and *step*. + + >>> iterable = iter('abcdefgh') + >>> list(islice_extended(iterable, -4, -1)) + ['e', 'f', 'g'] + + Slices with negative values require some caching of *iterable*, but this + function takes care to minimize the amount of memory required. + + For example, you can use a negative step with an infinite iterator: + + >>> from itertools import count + >>> list(islice_extended(count(), 110, 99, -2)) + [110, 108, 106, 104, 102, 100] + + You can also use slice notation directly: + + >>> iterable = map(str, count()) + >>> it = islice_extended(iterable)[10:20:2] + >>> list(it) + ['10', '12', '14', '16', '18'] + + """ + + def __init__(self, iterable, *args): + it = iter(iterable) + if args: + self._iterable = _islice_helper(it, slice(*args)) + else: + self._iterable = it + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterable) + + def __getitem__(self, key): + if isinstance(key, slice): + return islice_extended(_islice_helper(self._iterable, key)) + + raise TypeError('islice_extended.__getitem__ argument must be a slice') + + +def _islice_helper(it, s): + start = s.start + stop = s.stop + if s.step == 0: + raise ValueError('step argument must be a non-zero integer or None.') + step = s.step or 1 + + if step > 0: + start = 0 if (start is None) else start + + if start < 0: + # Consume all but the last -start items + cache = deque(enumerate(it, 1), maxlen=-start) + len_iter = cache[-1][0] if cache else 0 + + # Adjust start to be positive + i = max(len_iter + start, 0) + + # Adjust stop to be positive + if stop is None: + j = len_iter + elif stop >= 0: + j = min(stop, len_iter) + else: + j = max(len_iter + stop, 0) + + # Slice the cache + n = j - i + if n <= 0: + return + + for index, item in islice(cache, 0, n, step): + yield item + elif (stop is not None) and (stop < 0): + # Advance to the start position + next(islice(it, start, start), None) + + # When stop is negative, we have to carry -stop items while + # iterating + cache = deque(islice(it, -stop), maxlen=-stop) + + for index, item in enumerate(it): + cached_item = cache.popleft() + if index % step == 0: + yield cached_item + cache.append(item) + else: + # When both start and stop are positive we have the normal case + yield from islice(it, start, stop, step) + else: + start = -1 if (start is None) else start + + if (stop is not None) and (stop < 0): + # Consume all but the last items + n = -stop - 1 + cache = deque(enumerate(it, 1), maxlen=n) + len_iter = cache[-1][0] if cache else 0 + + # If start and stop are both negative they are comparable and + # we can just slice. Otherwise we can adjust start to be negative + # and then slice. + if start < 0: + i, j = start, stop + else: + i, j = min(start - len_iter, -1), None + + for index, item in list(cache)[i:j:step]: + yield item + else: + # Advance to the stop position + if stop is not None: + m = stop + 1 + next(islice(it, m, m), None) + + # stop is positive, so if start is negative they are not comparable + # and we need the rest of the items. + if start < 0: + i = start + n = None + # stop is None and start is positive, so we just need items up to + # the start index. + elif stop is None: + i = None + n = start + 1 + # Both stop and start are positive, so they are comparable. + else: + i = None + n = start - stop + if n <= 0: + return + + cache = list(islice(it, n)) + + yield from cache[i::step] + + +def always_reversible(iterable): + """An extension of :func:`reversed` that supports all iterables, not + just those which implement the ``Reversible`` or ``Sequence`` protocols. + + >>> print(*always_reversible(x for x in range(3))) + 2 1 0 + + If the iterable is already reversible, this function returns the + result of :func:`reversed()`. If the iterable is not reversible, + this function will cache the remaining items in the iterable and + yield them in reverse order, which may require significant storage. + """ + try: + return reversed(iterable) + except TypeError: + return reversed(list(iterable)) + + +def consecutive_groups(iterable, ordering=lambda x: x): + """Yield groups of consecutive items using :func:`itertools.groupby`. + The *ordering* function determines whether two items are adjacent by + returning their position. + + By default, the ordering function is the identity function. This is + suitable for finding runs of numbers: + + >>> iterable = [1, 10, 11, 12, 20, 30, 31, 32, 33, 40] + >>> for group in consecutive_groups(iterable): + ... print(list(group)) + [1] + [10, 11, 12] + [20] + [30, 31, 32, 33] + [40] + + For finding runs of adjacent letters, try using the :meth:`index` method + of a string of letters: + + >>> from string import ascii_lowercase + >>> iterable = 'abcdfgilmnop' + >>> ordering = ascii_lowercase.index + >>> for group in consecutive_groups(iterable, ordering): + ... print(list(group)) + ['a', 'b', 'c', 'd'] + ['f', 'g'] + ['i'] + ['l', 'm', 'n', 'o', 'p'] + + Each group of consecutive items is an iterator that shares it source with + *iterable*. When an an output group is advanced, the previous group is + no longer available unless its elements are copied (e.g., into a ``list``). + + >>> iterable = [1, 2, 11, 12, 21, 22] + >>> saved_groups = [] + >>> for group in consecutive_groups(iterable): + ... saved_groups.append(list(group)) # Copy group elements + >>> saved_groups + [[1, 2], [11, 12], [21, 22]] + + """ + for k, g in groupby( + enumerate(iterable), key=lambda x: x[0] - ordering(x[1]) + ): + yield map(itemgetter(1), g) + + +def difference(iterable, func=sub, *, initial=None): + """This function is the inverse of :func:`itertools.accumulate`. By default + it will compute the first difference of *iterable* using + :func:`operator.sub`: + + >>> from itertools import accumulate + >>> iterable = accumulate([0, 1, 2, 3, 4]) # produces 0, 1, 3, 6, 10 + >>> list(difference(iterable)) + [0, 1, 2, 3, 4] + + *func* defaults to :func:`operator.sub`, but other functions can be + specified. They will be applied as follows:: + + A, B, C, D, ... --> A, func(B, A), func(C, B), func(D, C), ... + + For example, to do progressive division: + + >>> iterable = [1, 2, 6, 24, 120] + >>> func = lambda x, y: x // y + >>> list(difference(iterable, func)) + [1, 2, 3, 4, 5] + + If the *initial* keyword is set, the first element will be skipped when + computing successive differences. + + >>> it = [10, 11, 13, 16] # from accumulate([1, 2, 3], initial=10) + >>> list(difference(it, initial=10)) + [1, 2, 3] + + """ + a, b = tee(iterable) + try: + first = [next(b)] + except StopIteration: + return iter([]) + + if initial is not None: + first = [] + + return chain(first, map(func, b, a)) + + +class SequenceView(Sequence): + """Return a read-only view of the sequence object *target*. + + :class:`SequenceView` objects are analogous to Python's built-in + "dictionary view" types. They provide a dynamic view of a sequence's items, + meaning that when the sequence updates, so does the view. + + >>> seq = ['0', '1', '2'] + >>> view = SequenceView(seq) + >>> view + SequenceView(['0', '1', '2']) + >>> seq.append('3') + >>> view + SequenceView(['0', '1', '2', '3']) + + Sequence views support indexing, slicing, and length queries. They act + like the underlying sequence, except they don't allow assignment: + + >>> view[1] + '1' + >>> view[1:-1] + ['1', '2'] + >>> len(view) + 4 + + Sequence views are useful as an alternative to copying, as they don't + require (much) extra storage. + + """ + + def __init__(self, target): + if not isinstance(target, Sequence): + raise TypeError + self._target = target + + def __getitem__(self, index): + return self._target[index] + + def __len__(self): + return len(self._target) + + def __repr__(self): + return '{}({})'.format(self.__class__.__name__, repr(self._target)) + + +class seekable: + """Wrap an iterator to allow for seeking backward and forward. This + progressively caches the items in the source iterable so they can be + re-visited. + + Call :meth:`seek` with an index to seek to that position in the source + iterable. + + To "reset" an iterator, seek to ``0``: + + >>> from itertools import count + >>> it = seekable((str(n) for n in count())) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> it.seek(0) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> next(it) + '3' + + You can also seek forward: + + >>> it = seekable((str(n) for n in range(20))) + >>> it.seek(10) + >>> next(it) + '10' + >>> it.seek(20) # Seeking past the end of the source isn't a problem + >>> list(it) + [] + >>> it.seek(0) # Resetting works even after hitting the end + >>> next(it), next(it), next(it) + ('0', '1', '2') + + Call :meth:`peek` to look ahead one item without advancing the iterator: + + >>> it = seekable('1234') + >>> it.peek() + '1' + >>> list(it) + ['1', '2', '3', '4'] + >>> it.peek(default='empty') + 'empty' + + Before the iterator is at its end, calling :func:`bool` on it will return + ``True``. After it will return ``False``: + + >>> it = seekable('5678') + >>> bool(it) + True + >>> list(it) + ['5', '6', '7', '8'] + >>> bool(it) + False + + You may view the contents of the cache with the :meth:`elements` method. + That returns a :class:`SequenceView`, a view that updates automatically: + + >>> it = seekable((str(n) for n in range(10))) + >>> next(it), next(it), next(it) + ('0', '1', '2') + >>> elements = it.elements() + >>> elements + SequenceView(['0', '1', '2']) + >>> next(it) + '3' + >>> elements + SequenceView(['0', '1', '2', '3']) + + By default, the cache grows as the source iterable progresses, so beware of + wrapping very large or infinite iterables. Supply *maxlen* to limit the + size of the cache (this of course limits how far back you can seek). + + >>> from itertools import count + >>> it = seekable((str(n) for n in count()), maxlen=2) + >>> next(it), next(it), next(it), next(it) + ('0', '1', '2', '3') + >>> list(it.elements()) + ['2', '3'] + >>> it.seek(0) + >>> next(it), next(it), next(it), next(it) + ('2', '3', '4', '5') + >>> next(it) + '6' + + """ + + def __init__(self, iterable, maxlen=None): + self._source = iter(iterable) + if maxlen is None: + self._cache = [] + else: + self._cache = deque([], maxlen) + self._index = None + + def __iter__(self): + return self + + def __next__(self): + if self._index is not None: + try: + item = self._cache[self._index] + except IndexError: + self._index = None + else: + self._index += 1 + return item + + item = next(self._source) + self._cache.append(item) + return item + + def __bool__(self): + try: + self.peek() + except StopIteration: + return False + return True + + def peek(self, default=_marker): + try: + peeked = next(self) + except StopIteration: + if default is _marker: + raise + return default + if self._index is None: + self._index = len(self._cache) + self._index -= 1 + return peeked + + def elements(self): + return SequenceView(self._cache) + + def seek(self, index): + self._index = index + remainder = index - len(self._cache) + if remainder > 0: + consume(self, remainder) + + +class run_length: + """ + :func:`run_length.encode` compresses an iterable with run-length encoding. + It yields groups of repeated items with the count of how many times they + were repeated: + + >>> uncompressed = 'abbcccdddd' + >>> list(run_length.encode(uncompressed)) + [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + + :func:`run_length.decode` decompresses an iterable that was previously + compressed with run-length encoding. It yields the items of the + decompressed iterable: + + >>> compressed = [('a', 1), ('b', 2), ('c', 3), ('d', 4)] + >>> list(run_length.decode(compressed)) + ['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'] + + """ + + @staticmethod + def encode(iterable): + return ((k, ilen(g)) for k, g in groupby(iterable)) + + @staticmethod + def decode(iterable): + return chain.from_iterable(repeat(k, n) for k, n in iterable) + + +def exactly_n(iterable, n, predicate=bool): + """Return ``True`` if exactly ``n`` items in the iterable are ``True`` + according to the *predicate* function. + + >>> exactly_n([True, True, False], 2) + True + >>> exactly_n([True, True, False], 1) + False + >>> exactly_n([0, 1, 2, 3, 4, 5], 3, lambda x: x < 3) + True + + The iterable will be advanced until ``n + 1`` truthy items are encountered, + so avoid calling it on infinite iterables. + + """ + return len(take(n + 1, filter(predicate, iterable))) == n + + +def circular_shifts(iterable): + """Return a list of circular shifts of *iterable*. + + >>> circular_shifts(range(4)) + [(0, 1, 2, 3), (1, 2, 3, 0), (2, 3, 0, 1), (3, 0, 1, 2)] + """ + lst = list(iterable) + return take(len(lst), windowed(cycle(lst), len(lst))) + + +def make_decorator(wrapping_func, result_index=0): + """Return a decorator version of *wrapping_func*, which is a function that + modifies an iterable. *result_index* is the position in that function's + signature where the iterable goes. + + This lets you use itertools on the "production end," i.e. at function + definition. This can augment what the function returns without changing the + function's code. + + For example, to produce a decorator version of :func:`chunked`: + + >>> from more_itertools import chunked + >>> chunker = make_decorator(chunked, result_index=0) + >>> @chunker(3) + ... def iter_range(n): + ... return iter(range(n)) + ... + >>> list(iter_range(9)) + [[0, 1, 2], [3, 4, 5], [6, 7, 8]] + + To only allow truthy items to be returned: + + >>> truth_serum = make_decorator(filter, result_index=1) + >>> @truth_serum(bool) + ... def boolean_test(): + ... return [0, 1, '', ' ', False, True] + ... + >>> list(boolean_test()) + [1, ' ', True] + + The :func:`peekable` and :func:`seekable` wrappers make for practical + decorators: + + >>> from more_itertools import peekable + >>> peekable_function = make_decorator(peekable) + >>> @peekable_function() + ... def str_range(*args): + ... return (str(x) for x in range(*args)) + ... + >>> it = str_range(1, 20, 2) + >>> next(it), next(it), next(it) + ('1', '3', '5') + >>> it.peek() + '7' + >>> next(it) + '7' + + """ + # See https://sites.google.com/site/bbayles/index/decorator_factory for + # notes on how this works. + def decorator(*wrapping_args, **wrapping_kwargs): + def outer_wrapper(f): + def inner_wrapper(*args, **kwargs): + result = f(*args, **kwargs) + wrapping_args_ = list(wrapping_args) + wrapping_args_.insert(result_index, result) + return wrapping_func(*wrapping_args_, **wrapping_kwargs) + + return inner_wrapper + + return outer_wrapper + + return decorator + + +def map_reduce(iterable, keyfunc, valuefunc=None, reducefunc=None): + """Return a dictionary that maps the items in *iterable* to categories + defined by *keyfunc*, transforms them with *valuefunc*, and + then summarizes them by category with *reducefunc*. + + *valuefunc* defaults to the identity function if it is unspecified. + If *reducefunc* is unspecified, no summarization takes place: + + >>> keyfunc = lambda x: x.upper() + >>> result = map_reduce('abbccc', keyfunc) + >>> sorted(result.items()) + [('A', ['a']), ('B', ['b', 'b']), ('C', ['c', 'c', 'c'])] + + Specifying *valuefunc* transforms the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> result = map_reduce('abbccc', keyfunc, valuefunc) + >>> sorted(result.items()) + [('A', [1]), ('B', [1, 1]), ('C', [1, 1, 1])] + + Specifying *reducefunc* summarizes the categorized items: + + >>> keyfunc = lambda x: x.upper() + >>> valuefunc = lambda x: 1 + >>> reducefunc = sum + >>> result = map_reduce('abbccc', keyfunc, valuefunc, reducefunc) + >>> sorted(result.items()) + [('A', 1), ('B', 2), ('C', 3)] + + You may want to filter the input iterable before applying the map/reduce + procedure: + + >>> all_items = range(30) + >>> items = [x for x in all_items if 10 <= x <= 20] # Filter + >>> keyfunc = lambda x: x % 2 # Evens map to 0; odds to 1 + >>> categories = map_reduce(items, keyfunc=keyfunc) + >>> sorted(categories.items()) + [(0, [10, 12, 14, 16, 18, 20]), (1, [11, 13, 15, 17, 19])] + >>> summaries = map_reduce(items, keyfunc=keyfunc, reducefunc=sum) + >>> sorted(summaries.items()) + [(0, 90), (1, 75)] + + Note that all items in the iterable are gathered into a list before the + summarization step, which may require significant storage. + + The returned object is a :obj:`collections.defaultdict` with the + ``default_factory`` set to ``None``, such that it behaves like a normal + dictionary. + + """ + valuefunc = (lambda x: x) if (valuefunc is None) else valuefunc + + ret = defaultdict(list) + for item in iterable: + key = keyfunc(item) + value = valuefunc(item) + ret[key].append(value) + + if reducefunc is not None: + for key, value_list in ret.items(): + ret[key] = reducefunc(value_list) + + ret.default_factory = None + return ret + + +def rlocate(iterable, pred=bool, window_size=None): + """Yield the index of each item in *iterable* for which *pred* returns + ``True``, starting from the right and moving left. + + *pred* defaults to :func:`bool`, which will select truthy items: + + >>> list(rlocate([0, 1, 1, 0, 1, 0, 0])) # Truthy at 1, 2, and 4 + [4, 2, 1] + + Set *pred* to a custom function to, e.g., find the indexes for a particular + item: + + >>> iterable = iter('abcb') + >>> pred = lambda x: x == 'b' + >>> list(rlocate(iterable, pred)) + [3, 1] + + If *window_size* is given, then the *pred* function will be called with + that many items. This enables searching for sub-sequences: + + >>> iterable = [0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3] + >>> pred = lambda *args: args == (1, 2, 3) + >>> list(rlocate(iterable, pred=pred, window_size=3)) + [9, 5, 1] + + Beware, this function won't return anything for infinite iterables. + If *iterable* is reversible, ``rlocate`` will reverse it and search from + the right. Otherwise, it will search from the left and return the results + in reverse order. + + See :func:`locate` to for other example applications. + + """ + if window_size is None: + try: + len_iter = len(iterable) + return (len_iter - i - 1 for i in locate(reversed(iterable), pred)) + except TypeError: + pass + + return reversed(list(locate(iterable, pred, window_size))) + + +def replace(iterable, pred, substitutes, count=None, window_size=1): + """Yield the items from *iterable*, replacing the items for which *pred* + returns ``True`` with the items from the iterable *substitutes*. + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1] + >>> pred = lambda x: x == 0 + >>> substitutes = (2, 3) + >>> list(replace(iterable, pred, substitutes)) + [1, 1, 2, 3, 1, 1, 2, 3, 1, 1] + + If *count* is given, the number of replacements will be limited: + + >>> iterable = [1, 1, 0, 1, 1, 0, 1, 1, 0] + >>> pred = lambda x: x == 0 + >>> substitutes = [None] + >>> list(replace(iterable, pred, substitutes, count=2)) + [1, 1, None, 1, 1, None, 1, 1, 0] + + Use *window_size* to control the number of items passed as arguments to + *pred*. This allows for locating and replacing subsequences. + + >>> iterable = [0, 1, 2, 5, 0, 1, 2, 5] + >>> window_size = 3 + >>> pred = lambda *args: args == (0, 1, 2) # 3 items passed to pred + >>> substitutes = [3, 4] # Splice in these items + >>> list(replace(iterable, pred, substitutes, window_size=window_size)) + [3, 4, 5, 3, 4, 5] + + """ + if window_size < 1: + raise ValueError('window_size must be at least 1') + + # Save the substitutes iterable, since it's used more than once + substitutes = tuple(substitutes) + + # Add padding such that the number of windows matches the length of the + # iterable + it = chain(iterable, [_marker] * (window_size - 1)) + windows = windowed(it, window_size) + + n = 0 + for w in windows: + # If the current window matches our predicate (and we haven't hit + # our maximum number of replacements), splice in the substitutes + # and then consume the following windows that overlap with this one. + # For example, if the iterable is (0, 1, 2, 3, 4...) + # and the window size is 2, we have (0, 1), (1, 2), (2, 3)... + # If the predicate matches on (0, 1), we need to zap (0, 1) and (1, 2) + if pred(*w): + if (count is None) or (n < count): + n += 1 + yield from substitutes + consume(windows, window_size - 1) + continue + + # If there was no match (or we've reached the replacement limit), + # yield the first item from the window. + if w and (w[0] is not _marker): + yield w[0] + + +def partitions(iterable): + """Yield all possible order-preserving partitions of *iterable*. + + >>> iterable = 'abc' + >>> for part in partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['a', 'b', 'c'] + + This is unrelated to :func:`partition`. + + """ + sequence = list(iterable) + n = len(sequence) + for i in powerset(range(1, n)): + yield [sequence[i:j] for i, j in zip((0,) + i, i + (n,))] + + +def set_partitions(iterable, k=None): + """ + Yield the set partitions of *iterable* into *k* parts. Set partitions are + not order-preserving. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable, 2): + ... print([''.join(p) for p in part]) + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + + + If *k* is not given, every set partition is generated. + + >>> iterable = 'abc' + >>> for part in set_partitions(iterable): + ... print([''.join(p) for p in part]) + ['abc'] + ['a', 'bc'] + ['ab', 'c'] + ['b', 'ac'] + ['a', 'b', 'c'] + + """ + L = list(iterable) + n = len(L) + if k is not None: + if k < 1: + raise ValueError( + "Can't partition in a negative or zero number of groups" + ) + elif k > n: + return + + def set_partitions_helper(L, k): + n = len(L) + if k == 1: + yield [L] + elif n == k: + yield [[s] for s in L] + else: + e, *M = L + for p in set_partitions_helper(M, k - 1): + yield [[e], *p] + for p in set_partitions_helper(M, k): + for i in range(len(p)): + yield p[:i] + [[e] + p[i]] + p[i + 1 :] + + if k is None: + for k in range(1, n + 1): + yield from set_partitions_helper(L, k) + else: + yield from set_partitions_helper(L, k) + + +class time_limited: + """ + Yield items from *iterable* until *limit_seconds* have passed. + If the time limit expires before all items have been yielded, the + ``timed_out`` parameter will be set to ``True``. + + >>> from time import sleep + >>> def generator(): + ... yield 1 + ... yield 2 + ... sleep(0.2) + ... yield 3 + >>> iterable = time_limited(0.1, generator()) + >>> list(iterable) + [1, 2] + >>> iterable.timed_out + True + + Note that the time is checked before each item is yielded, and iteration + stops if the time elapsed is greater than *limit_seconds*. If your time + limit is 1 second, but it takes 2 seconds to generate the first item from + the iterable, the function will run for 2 seconds and not yield anything. + + """ + + def __init__(self, limit_seconds, iterable): + if limit_seconds < 0: + raise ValueError('limit_seconds must be positive') + self.limit_seconds = limit_seconds + self._iterable = iter(iterable) + self._start_time = monotonic() + self.timed_out = False + + def __iter__(self): + return self + + def __next__(self): + item = next(self._iterable) + if monotonic() - self._start_time > self.limit_seconds: + self.timed_out = True + raise StopIteration + + return item + + +def only(iterable, default=None, too_long=None): + """If *iterable* has only one item, return it. + If it has zero items, return *default*. + If it has more than one item, raise the exception given by *too_long*, + which is ``ValueError`` by default. + + >>> only([], default='missing') + 'missing' + >>> only([1]) + 1 + >>> only([1, 2]) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValueError: Expected exactly one item in iterable, but got 1, 2, + and perhaps more.' + >>> only([1, 2], too_long=TypeError) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + TypeError + + Note that :func:`only` attempts to advance *iterable* twice to ensure there + is only one item. See :func:`spy` or :func:`peekable` to check + iterable contents less destructively. + """ + it = iter(iterable) + first_value = next(it, default) + + try: + second_value = next(it) + except StopIteration: + pass + else: + msg = ( + 'Expected exactly one item in iterable, but got {!r}, {!r}, ' + 'and perhaps more.'.format(first_value, second_value) + ) + raise too_long or ValueError(msg) + + return first_value + + +class _IChunk: + def __init__(self, iterable, n): + self._it = islice(iterable, n) + self._cache = deque() + + def fill_cache(self): + self._cache.extend(self._it) + + def __iter__(self): + return self + + def __next__(self): + try: + return next(self._it) + except StopIteration: + if self._cache: + return self._cache.popleft() + else: + raise + + +def ichunked(iterable, n): + """Break *iterable* into sub-iterables with *n* elements each. + :func:`ichunked` is like :func:`chunked`, but it yields iterables + instead of lists. + + If the sub-iterables are read in order, the elements of *iterable* + won't be stored in memory. + If they are read out of order, :func:`itertools.tee` is used to cache + elements as necessary. + + >>> from itertools import count + >>> all_chunks = ichunked(count(), 4) + >>> c_1, c_2, c_3 = next(all_chunks), next(all_chunks), next(all_chunks) + >>> list(c_2) # c_1's elements have been cached; c_3's haven't been + [4, 5, 6, 7] + >>> list(c_1) + [0, 1, 2, 3] + >>> list(c_3) + [8, 9, 10, 11] + + """ + source = peekable(iter(iterable)) + ichunk_marker = object() + while True: + # Check to see whether we're at the end of the source iterable + item = source.peek(ichunk_marker) + if item is ichunk_marker: + return + + chunk = _IChunk(source, n) + yield chunk + + # Advance the source iterable and fill previous chunk's cache + chunk.fill_cache() + + +def iequals(*iterables): + """Return ``True`` if all given *iterables* are equal to each other, + which means that they contain the same elements in the same order. + + The function is useful for comparing iterables of different data types + or iterables that do not support equality checks. + + >>> iequals("abc", ['a', 'b', 'c'], ('a', 'b', 'c'), iter("abc")) + True + + >>> iequals("abc", "acb") + False + + Not to be confused with :func:`all_equals`, which checks whether all + elements of iterable are equal to each other. + + """ + return all(map(all_equal, zip_longest(*iterables, fillvalue=object()))) + + +def distinct_combinations(iterable, r): + """Yield the distinct combinations of *r* items taken from *iterable*. + + >>> list(distinct_combinations([0, 0, 1], 2)) + [(0, 0), (0, 1)] + + Equivalent to ``set(combinations(iterable))``, except duplicates are not + generated and thrown away. For larger input sequences this is much more + efficient. + + """ + if r < 0: + raise ValueError('r must be non-negative') + elif r == 0: + yield () + return + pool = tuple(iterable) + generators = [unique_everseen(enumerate(pool), key=itemgetter(1))] + current_combo = [None] * r + level = 0 + while generators: + try: + cur_idx, p = next(generators[-1]) + except StopIteration: + generators.pop() + level -= 1 + continue + current_combo[level] = p + if level + 1 == r: + yield tuple(current_combo) + else: + generators.append( + unique_everseen( + enumerate(pool[cur_idx + 1 :], cur_idx + 1), + key=itemgetter(1), + ) + ) + level += 1 + + +def filter_except(validator, iterable, *exceptions): + """Yield the items from *iterable* for which the *validator* function does + not raise one of the specified *exceptions*. + + *validator* is called for each item in *iterable*. + It should be a function that accepts one argument and raises an exception + if that item is not valid. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(filter_except(int, iterable, ValueError, TypeError)) + ['1', '2', '4'] + + If an exception other than one given by *exceptions* is raised by + *validator*, it is raised like normal. + """ + for item in iterable: + try: + validator(item) + except exceptions: + pass + else: + yield item + + +def map_except(function, iterable, *exceptions): + """Transform each item from *iterable* with *function* and yield the + result, unless *function* raises one of the specified *exceptions*. + + *function* is called to transform each item in *iterable*. + It should accept one argument. + + >>> iterable = ['1', '2', 'three', '4', None] + >>> list(map_except(int, iterable, ValueError, TypeError)) + [1, 2, 4] + + If an exception other than one given by *exceptions* is raised by + *function*, it is raised like normal. + """ + for item in iterable: + try: + yield function(item) + except exceptions: + pass + + +def map_if(iterable, pred, func, func_else=lambda x: x): + """Evaluate each item from *iterable* using *pred*. If the result is + equivalent to ``True``, transform the item with *func* and yield it. + Otherwise, transform the item with *func_else* and yield it. + + *pred*, *func*, and *func_else* should each be functions that accept + one argument. By default, *func_else* is the identity function. + + >>> from math import sqrt + >>> iterable = list(range(-5, 5)) + >>> iterable + [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] + >>> list(map_if(iterable, lambda x: x > 3, lambda x: 'toobig')) + [-5, -4, -3, -2, -1, 0, 1, 2, 3, 'toobig'] + >>> list(map_if(iterable, lambda x: x >= 0, + ... lambda x: f'{sqrt(x):.2f}', lambda x: None)) + [None, None, None, None, None, '0.00', '1.00', '1.41', '1.73', '2.00'] + """ + for item in iterable: + yield func(item) if pred(item) else func_else(item) + + +def _sample_unweighted(iterable, k): + # Implementation of "Algorithm L" from the 1994 paper by Kim-Hung Li: + # "Reservoir-Sampling Algorithms of Time Complexity O(n(1+log(N/n)))". + + # Fill up the reservoir (collection of samples) with the first `k` samples + reservoir = take(k, iterable) + + # Generate random number that's the largest in a sample of k U(0,1) numbers + # Largest order statistic: https://en.wikipedia.org/wiki/Order_statistic + W = exp(log(random()) / k) + + # The number of elements to skip before changing the reservoir is a random + # number with a geometric distribution. Sample it using random() and logs. + next_index = k + floor(log(random()) / log(1 - W)) + + for index, element in enumerate(iterable, k): + + if index == next_index: + reservoir[randrange(k)] = element + # The new W is the largest in a sample of k U(0, `old_W`) numbers + W *= exp(log(random()) / k) + next_index += floor(log(random()) / log(1 - W)) + 1 + + return reservoir + + +def _sample_weighted(iterable, k, weights): + # Implementation of "A-ExpJ" from the 2006 paper by Efraimidis et al. : + # "Weighted random sampling with a reservoir". + + # Log-transform for numerical stability for weights that are small/large + weight_keys = (log(random()) / weight for weight in weights) + + # Fill up the reservoir (collection of samples) with the first `k` + # weight-keys and elements, then heapify the list. + reservoir = take(k, zip(weight_keys, iterable)) + heapify(reservoir) + + # The number of jumps before changing the reservoir is a random variable + # with an exponential distribution. Sample it using random() and logs. + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + + for weight, element in zip(weights, iterable): + if weight >= weights_to_skip: + # The notation here is consistent with the paper, but we store + # the weight-keys in log-space for better numerical stability. + smallest_weight_key, _ = reservoir[0] + t_w = exp(weight * smallest_weight_key) + r_2 = uniform(t_w, 1) # generate U(t_w, 1) + weight_key = log(r_2) / weight + heapreplace(reservoir, (weight_key, element)) + smallest_weight_key, _ = reservoir[0] + weights_to_skip = log(random()) / smallest_weight_key + else: + weights_to_skip -= weight + + # Equivalent to [element for weight_key, element in sorted(reservoir)] + return [heappop(reservoir)[1] for _ in range(k)] + + +def sample(iterable, k, weights=None): + """Return a *k*-length list of elements chosen (without replacement) + from the *iterable*. Like :func:`random.sample`, but works on iterables + of unknown length. + + >>> iterable = range(100) + >>> sample(iterable, 5) # doctest: +SKIP + [81, 60, 96, 16, 4] + + An iterable with *weights* may also be given: + + >>> iterable = range(100) + >>> weights = (i * i + 1 for i in range(100)) + >>> sampled = sample(iterable, 5, weights=weights) # doctest: +SKIP + [79, 67, 74, 66, 78] + + The algorithm can also be used to generate weighted random permutations. + The relative weight of each item determines the probability that it + appears late in the permutation. + + >>> data = "abcdefgh" + >>> weights = range(1, len(data) + 1) + >>> sample(data, k=len(data), weights=weights) # doctest: +SKIP + ['c', 'a', 'b', 'e', 'g', 'd', 'h', 'f'] + """ + if k == 0: + return [] + + iterable = iter(iterable) + if weights is None: + return _sample_unweighted(iterable, k) + else: + weights = iter(weights) + return _sample_weighted(iterable, k, weights) + + +def is_sorted(iterable, key=None, reverse=False, strict=False): + """Returns ``True`` if the items of iterable are in sorted order, and + ``False`` otherwise. *key* and *reverse* have the same meaning that they do + in the built-in :func:`sorted` function. + + >>> is_sorted(['1', '2', '3', '4', '5'], key=int) + True + >>> is_sorted([5, 4, 3, 1, 2], reverse=True) + False + + If *strict*, tests for strict sorting, that is, returns ``False`` if equal + elements are found: + + >>> is_sorted([1, 2, 2]) + True + >>> is_sorted([1, 2, 2], strict=True) + False + + The function returns ``False`` after encountering the first out-of-order + item. If there are no out-of-order items, the iterable is exhausted. + """ + + compare = (le if reverse else ge) if strict else (lt if reverse else gt) + it = iterable if key is None else map(key, iterable) + return not any(starmap(compare, pairwise(it))) + + +class AbortThread(BaseException): + pass + + +class callback_iter: + """Convert a function that uses callbacks to an iterator. + + Let *func* be a function that takes a `callback` keyword argument. + For example: + + >>> def func(callback=None): + ... for i, c in [(1, 'a'), (2, 'b'), (3, 'c')]: + ... if callback: + ... callback(i, c) + ... return 4 + + + Use ``with callback_iter(func)`` to get an iterator over the parameters + that are delivered to the callback. + + >>> with callback_iter(func) as it: + ... for args, kwargs in it: + ... print(args) + (1, 'a') + (2, 'b') + (3, 'c') + + The function will be called in a background thread. The ``done`` property + indicates whether it has completed execution. + + >>> it.done + True + + If it completes successfully, its return value will be available + in the ``result`` property. + + >>> it.result + 4 + + Notes: + + * If the function uses some keyword argument besides ``callback``, supply + *callback_kwd*. + * If it finished executing, but raised an exception, accessing the + ``result`` property will raise the same exception. + * If it hasn't finished executing, accessing the ``result`` + property from within the ``with`` block will raise ``RuntimeError``. + * If it hasn't finished executing, accessing the ``result`` property from + outside the ``with`` block will raise a + ``more_itertools.AbortThread`` exception. + * Provide *wait_seconds* to adjust how frequently the it is polled for + output. + + """ + + def __init__(self, func, callback_kwd='callback', wait_seconds=0.1): + self._func = func + self._callback_kwd = callback_kwd + self._aborted = False + self._future = None + self._wait_seconds = wait_seconds + # Lazily import concurrent.future + self._executor = __import__( + 'concurrent.futures' + ).futures.ThreadPoolExecutor(max_workers=1) + self._iterator = self._reader() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._aborted = True + self._executor.shutdown() + + def __iter__(self): + return self + + def __next__(self): + return next(self._iterator) + + @property + def done(self): + if self._future is None: + return False + return self._future.done() + + @property + def result(self): + if not self.done: + raise RuntimeError('Function has not yet completed') + + return self._future.result() + + def _reader(self): + q = Queue() + + def callback(*args, **kwargs): + if self._aborted: + raise AbortThread('canceled by user') + + q.put((args, kwargs)) + + self._future = self._executor.submit( + self._func, **{self._callback_kwd: callback} + ) + + while True: + try: + item = q.get(timeout=self._wait_seconds) + except Empty: + pass + else: + q.task_done() + yield item + + if self._future.done(): + break + + remaining = [] + while True: + try: + item = q.get_nowait() + except Empty: + break + else: + q.task_done() + remaining.append(item) + q.join() + yield from remaining + + +def windowed_complete(iterable, n): + """ + Yield ``(beginning, middle, end)`` tuples, where: + + * Each ``middle`` has *n* items from *iterable* + * Each ``beginning`` has the items before the ones in ``middle`` + * Each ``end`` has the items after the ones in ``middle`` + + >>> iterable = range(7) + >>> n = 3 + >>> for beginning, middle, end in windowed_complete(iterable, n): + ... print(beginning, middle, end) + () (0, 1, 2) (3, 4, 5, 6) + (0,) (1, 2, 3) (4, 5, 6) + (0, 1) (2, 3, 4) (5, 6) + (0, 1, 2) (3, 4, 5) (6,) + (0, 1, 2, 3) (4, 5, 6) () + + Note that *n* must be at least 0 and most equal to the length of + *iterable*. + + This function will exhaust the iterable and may require significant + storage. + """ + if n < 0: + raise ValueError('n must be >= 0') + + seq = tuple(iterable) + size = len(seq) + + if n > size: + raise ValueError('n must be <= len(seq)') + + for i in range(size - n + 1): + beginning = seq[:i] + middle = seq[i : i + n] + end = seq[i + n :] + yield beginning, middle, end + + +def all_unique(iterable, key=None): + """ + Returns ``True`` if all the elements of *iterable* are unique (no two + elements are equal). + + >>> all_unique('ABCB') + False + + If a *key* function is specified, it will be used to make comparisons. + + >>> all_unique('ABCb') + True + >>> all_unique('ABCb', str.lower) + False + + The function returns as soon as the first non-unique element is + encountered. Iterables with a mix of hashable and unhashable items can + be used, but the function will be slower for unhashable items. + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + for element in map(key, iterable) if key else iterable: + try: + if element in seenset: + return False + seenset_add(element) + except TypeError: + if element in seenlist: + return False + seenlist_add(element) + return True + + +def nth_product(index, *args): + """Equivalent to ``list(product(*args))[index]``. + + The products of *args* can be ordered lexicographically. + :func:`nth_product` computes the product at sort position *index* without + computing the previous products. + + >>> nth_product(8, range(2), range(2), range(2), range(2)) + (1, 0, 0, 0) + + ``IndexError`` will be raised if the given *index* is invalid. + """ + pools = list(map(tuple, reversed(args))) + ns = list(map(len, pools)) + + c = reduce(mul, ns) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + result = [] + for pool, n in zip(pools, ns): + result.append(pool[index % n]) + index //= n + + return tuple(reversed(result)) + + +def nth_permutation(iterable, r, index): + """Equivalent to ``list(permutations(iterable, r))[index]``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`nth_permutation` + computes the subsequence at sort position *index* directly, without + computing the previous subsequences. + + >>> nth_permutation('ghijk', 2, 5) + ('h', 'i') + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = list(iterable) + n = len(pool) + + if r is None or r == n: + r, c = n, factorial(n) + elif not 0 <= r < n: + raise ValueError + else: + c = factorial(n) // factorial(n - r) + + if index < 0: + index += c + + if not 0 <= index < c: + raise IndexError + + if c == 0: + return tuple() + + result = [0] * r + q = index * factorial(n) // c if r < n else index + for d in range(1, n + 1): + q, i = divmod(q, d) + if 0 <= n - d < r: + result[n - d] = i + if q == 0: + break + + return tuple(map(pool.pop, result)) + + +def value_chain(*args): + """Yield all arguments passed to the function in the same order in which + they were passed. If an argument itself is iterable then iterate over its + values. + + >>> list(value_chain(1, 2, 3, [4, 5, 6])) + [1, 2, 3, 4, 5, 6] + + Binary and text strings are not considered iterable and are emitted + as-is: + + >>> list(value_chain('12', '34', ['56', '78'])) + ['12', '34', '56', '78'] + + + Multiple levels of nesting are not flattened. + + """ + for value in args: + if isinstance(value, (str, bytes)): + yield value + continue + try: + yield from value + except TypeError: + yield value + + +def product_index(element, *args): + """Equivalent to ``list(product(*args)).index(element)`` + + The products of *args* can be ordered lexicographically. + :func:`product_index` computes the first index of *element* without + computing the previous products. + + >>> product_index([8, 2], range(10), range(5)) + 42 + + ``ValueError`` will be raised if the given *element* isn't in the product + of *args*. + """ + index = 0 + + for x, pool in zip_longest(element, args, fillvalue=_marker): + if x is _marker or pool is _marker: + raise ValueError('element is not a product of args') + + pool = tuple(pool) + index = index * len(pool) + pool.index(x) + + return index + + +def combination_index(element, iterable): + """Equivalent to ``list(combinations(iterable, r)).index(element)`` + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`combination_index` computes the index of the + first *element*, without computing the previous combinations. + + >>> combination_index('adf', 'abcdefg') + 10 + + ``ValueError`` will be raised if the given *element* isn't one of the + combinations of *iterable*. + """ + element = enumerate(element) + k, y = next(element, (None, None)) + if k is None: + return 0 + + indexes = [] + pool = enumerate(iterable) + for n, x in pool: + if x == y: + indexes.append(n) + tmp, y = next(element, (None, None)) + if tmp is None: + break + else: + k = tmp + else: + raise ValueError('element is not a combination of iterable') + + n, _ = last(pool, default=(n, None)) + + # Python versions below 3.8 don't have math.comb + index = 1 + for i, j in enumerate(reversed(indexes), start=1): + j = n - j + if i <= j: + index += factorial(j) // (factorial(i) * factorial(j - i)) + + return factorial(n + 1) // (factorial(k + 1) * factorial(n - k)) - index + + +def permutation_index(element, iterable): + """Equivalent to ``list(permutations(iterable, r)).index(element)``` + + The subsequences of *iterable* that are of length *r* where order is + important can be ordered lexicographically. :func:`permutation_index` + computes the index of the first *element* directly, without computing + the previous permutations. + + >>> permutation_index([1, 3, 2], range(5)) + 19 + + ``ValueError`` will be raised if the given *element* isn't one of the + permutations of *iterable*. + """ + index = 0 + pool = list(iterable) + for i, x in zip(range(len(pool), -1, -1), element): + r = pool.index(x) + index = index * i + r + del pool[r] + + return index + + +class countable: + """Wrap *iterable* and keep a count of how many items have been consumed. + + The ``items_seen`` attribute starts at ``0`` and increments as the iterable + is consumed: + + >>> iterable = map(str, range(10)) + >>> it = countable(iterable) + >>> it.items_seen + 0 + >>> next(it), next(it) + ('0', '1') + >>> list(it) + ['2', '3', '4', '5', '6', '7', '8', '9'] + >>> it.items_seen + 10 + """ + + def __init__(self, iterable): + self._it = iter(iterable) + self.items_seen = 0 + + def __iter__(self): + return self + + def __next__(self): + item = next(self._it) + self.items_seen += 1 + + return item + + +def chunked_even(iterable, n): + """Break *iterable* into lists of approximately length *n*. + Items are distributed such the lengths of the lists differ by at most + 1 item. + + >>> iterable = [1, 2, 3, 4, 5, 6, 7] + >>> n = 3 + >>> list(chunked_even(iterable, n)) # List lengths: 3, 2, 2 + [[1, 2, 3], [4, 5], [6, 7]] + >>> list(chunked(iterable, n)) # List lengths: 3, 3, 1 + [[1, 2, 3], [4, 5, 6], [7]] + + """ + + len_method = getattr(iterable, '__len__', None) + + if len_method is None: + return _chunked_even_online(iterable, n) + else: + return _chunked_even_finite(iterable, len_method(), n) + + +def _chunked_even_online(iterable, n): + buffer = [] + maxbuf = n + (n - 2) * (n - 1) + for x in iterable: + buffer.append(x) + if len(buffer) == maxbuf: + yield buffer[:n] + buffer = buffer[n:] + yield from _chunked_even_finite(buffer, len(buffer), n) + + +def _chunked_even_finite(iterable, N, n): + if N < 1: + return + + # Lists are either size `full_size <= n` or `partial_size = full_size - 1` + q, r = divmod(N, n) + num_lists = q + (1 if r > 0 else 0) + q, r = divmod(N, num_lists) + full_size = q + (1 if r > 0 else 0) + partial_size = full_size - 1 + num_full = N - partial_size * num_lists + num_partial = num_lists - num_full + + buffer = [] + iterator = iter(iterable) + + # Yield num_full lists of full_size + for x in iterator: + buffer.append(x) + if len(buffer) == full_size: + yield buffer + buffer = [] + num_full -= 1 + if num_full <= 0: + break + + # Yield num_partial lists of partial_size + for x in iterator: + buffer.append(x) + if len(buffer) == partial_size: + yield buffer + buffer = [] + num_partial -= 1 + + +def zip_broadcast(*objects, scalar_types=(str, bytes), strict=False): + """A version of :func:`zip` that "broadcasts" any scalar + (i.e., non-iterable) items into output tuples. + + >>> iterable_1 = [1, 2, 3] + >>> iterable_2 = ['a', 'b', 'c'] + >>> scalar = '_' + >>> list(zip_broadcast(iterable_1, iterable_2, scalar)) + [(1, 'a', '_'), (2, 'b', '_'), (3, 'c', '_')] + + The *scalar_types* keyword argument determines what types are considered + scalar. It is set to ``(str, bytes)`` by default. Set it to ``None`` to + treat strings and byte strings as iterable: + + >>> list(zip_broadcast('abc', 0, 'xyz', scalar_types=None)) + [('a', 0, 'x'), ('b', 0, 'y'), ('c', 0, 'z')] + + If the *strict* keyword argument is ``True``, then + ``UnequalIterablesError`` will be raised if any of the iterables have + different lengths. + """ + + def is_scalar(obj): + if scalar_types and isinstance(obj, scalar_types): + return True + try: + iter(obj) + except TypeError: + return True + else: + return False + + size = len(objects) + if not size: + return + + iterables, iterable_positions = [], [] + scalars, scalar_positions = [], [] + for i, obj in enumerate(objects): + if is_scalar(obj): + scalars.append(obj) + scalar_positions.append(i) + else: + iterables.append(iter(obj)) + iterable_positions.append(i) + + if len(scalars) == size: + yield tuple(objects) + return + + zipper = _zip_equal if strict else zip + for item in zipper(*iterables): + new_item = [None] * size + + for i, elem in zip(iterable_positions, item): + new_item[i] = elem + + for i, elem in zip(scalar_positions, scalars): + new_item[i] = elem + + yield tuple(new_item) + + +def unique_in_window(iterable, n, key=None): + """Yield the items from *iterable* that haven't been seen recently. + *n* is the size of the lookback window. + + >>> iterable = [0, 1, 0, 2, 3, 0] + >>> n = 3 + >>> list(unique_in_window(iterable, n)) + [0, 1, 2, 3, 0] + + The *key* function, if provided, will be used to determine uniqueness: + + >>> list(unique_in_window('abAcda', 3, key=lambda x: x.lower())) + ['a', 'b', 'c', 'd', 'a'] + + The items in *iterable* must be hashable. + + """ + if n <= 0: + raise ValueError('n must be greater than 0') + + window = deque(maxlen=n) + uniques = set() + use_key = key is not None + + for item in iterable: + k = key(item) if use_key else item + if k in uniques: + continue + + if len(uniques) == n: + uniques.discard(window[0]) + + uniques.add(k) + window.append(k) + + yield item + + +def duplicates_everseen(iterable, key=None): + """Yield duplicate elements after their first appearance. + + >>> list(duplicates_everseen('mississippi')) + ['s', 'i', 's', 's', 'i', 'p', 'i'] + >>> list(duplicates_everseen('AaaBbbCccAaa', str.lower)) + ['a', 'a', 'b', 'b', 'c', 'c', 'A', 'a', 'a'] + + This function is analagous to :func:`unique_everseen` and is subject to + the same performance considerations. + + """ + seen_set = set() + seen_list = [] + use_key = key is not None + + for element in iterable: + k = key(element) if use_key else element + try: + if k not in seen_set: + seen_set.add(k) + else: + yield element + except TypeError: + if k not in seen_list: + seen_list.append(k) + else: + yield element + + +def duplicates_justseen(iterable, key=None): + """Yields serially-duplicate elements after their first appearance. + + >>> list(duplicates_justseen('mississippi')) + ['s', 's', 'p'] + >>> list(duplicates_justseen('AaaBbbCccAaa', str.lower)) + ['a', 'a', 'b', 'b', 'c', 'c', 'a', 'a'] + + This function is analagous to :func:`unique_justseen`. + + """ + return flatten( + map( + lambda group_tuple: islice_extended(group_tuple[1])[1:], + groupby(iterable, key), + ) + ) + + +def minmax(iterable_or_value, *others, key=None, default=_marker): + """Returns both the smallest and largest items in an iterable + or the largest of two or more arguments. + + >>> minmax([3, 1, 5]) + (1, 5) + + >>> minmax(4, 2, 6) + (2, 6) + + If a *key* function is provided, it will be used to transform the input + items for comparison. + + >>> minmax([5, 30], key=str) # '30' sorts before '5' + (30, 5) + + If a *default* value is provided, it will be returned if there are no + input items. + + >>> minmax([], default=(0, 0)) + (0, 0) + + Otherwise ``ValueError`` is raised. + + This function is based on the + `recipe `__ by + Raymond Hettinger and takes care to minimize the number of comparisons + performed. + """ + iterable = (iterable_or_value, *others) if others else iterable_or_value + + it = iter(iterable) + + try: + lo = hi = next(it) + except StopIteration as e: + if default is _marker: + raise ValueError( + '`minmax()` argument is an empty iterable. ' + 'Provide a `default` value to suppress this error.' + ) from e + return default + + # Different branches depending on the presence of key. This saves a lot + # of unimportant copies which would slow the "key=None" branch + # significantly down. + if key is None: + for x, y in zip_longest(it, it, fillvalue=lo): + if y < x: + x, y = y, x + if x < lo: + lo = x + if hi < y: + hi = y + + else: + lo_key = hi_key = key(lo) + + for x, y in zip_longest(it, it, fillvalue=lo): + + x_key, y_key = key(x), key(y) + + if y_key < x_key: + x, y, x_key, y_key = y, x, y_key, x_key + if x_key < lo_key: + lo, lo_key = x, x_key + if hi_key < y_key: + hi, hi_key = y, y_key + + return lo, hi + + +def constrained_batches( + iterable, max_size, max_count=None, get_len=len, strict=True +): + """Yield batches of items from *iterable* with a combined size limited by + *max_size*. + + >>> iterable = [b'12345', b'123', b'12345678', b'1', b'1', b'12', b'1'] + >>> list(constrained_batches(iterable, 10)) + [(b'12345', b'123'), (b'12345678', b'1', b'1'), (b'12', b'1')] + + If a *max_count* is supplied, the number of items per batch is also + limited: + + >>> iterable = [b'12345', b'123', b'12345678', b'1', b'1', b'12', b'1'] + >>> list(constrained_batches(iterable, 10, max_count = 2)) + [(b'12345', b'123'), (b'12345678', b'1'), (b'1', b'12'), (b'1',)] + + If a *get_len* function is supplied, use that instead of :func:`len` to + determine item size. + + If *strict* is ``True``, raise ``ValueError`` if any single item is bigger + than *max_size*. Otherwise, allow single items to exceed *max_size*. + """ + if max_size <= 0: + raise ValueError('maximum size must be greater than zero') + + batch = [] + batch_size = 0 + batch_count = 0 + for item in iterable: + item_len = get_len(item) + if strict and item_len > max_size: + raise ValueError('item size exceeds maximum size') + + reached_count = batch_count == max_count + reached_size = item_len + batch_size > max_size + if batch_count and (reached_size or reached_count): + yield tuple(batch) + batch.clear() + batch_size = 0 + batch_count = 0 + + batch.append(item) + batch_size += item_len + batch_count += 1 + + if batch: + yield tuple(batch) diff --git a/libs/common/more_itertools/more.pyi b/libs/common/more_itertools/more.pyi new file mode 100644 index 00000000..1413fae7 --- /dev/null +++ b/libs/common/more_itertools/more.pyi @@ -0,0 +1,674 @@ +"""Stubs for more_itertools.more""" + +from typing import ( + Any, + Callable, + Container, + Dict, + Generic, + Hashable, + Iterable, + Iterator, + List, + Optional, + Reversible, + Sequence, + Sized, + Tuple, + Union, + TypeVar, + type_check_only, +) +from types import TracebackType +from typing_extensions import ContextManager, Protocol, Type, overload + +# Type and type variable definitions +_T = TypeVar('_T') +_T1 = TypeVar('_T1') +_T2 = TypeVar('_T2') +_U = TypeVar('_U') +_V = TypeVar('_V') +_W = TypeVar('_W') +_T_co = TypeVar('_T_co', covariant=True) +_GenFn = TypeVar('_GenFn', bound=Callable[..., Iterator[object]]) +_Raisable = Union[BaseException, 'Type[BaseException]'] + +@type_check_only +class _SizedIterable(Protocol[_T_co], Sized, Iterable[_T_co]): ... + +@type_check_only +class _SizedReversible(Protocol[_T_co], Sized, Reversible[_T_co]): ... + +def chunked( + iterable: Iterable[_T], n: Optional[int], strict: bool = ... +) -> Iterator[List[_T]]: ... +@overload +def first(iterable: Iterable[_T]) -> _T: ... +@overload +def first(iterable: Iterable[_T], default: _U) -> Union[_T, _U]: ... +@overload +def last(iterable: Iterable[_T]) -> _T: ... +@overload +def last(iterable: Iterable[_T], default: _U) -> Union[_T, _U]: ... +@overload +def nth_or_last(iterable: Iterable[_T], n: int) -> _T: ... +@overload +def nth_or_last( + iterable: Iterable[_T], n: int, default: _U +) -> Union[_T, _U]: ... + +class peekable(Generic[_T], Iterator[_T]): + def __init__(self, iterable: Iterable[_T]) -> None: ... + def __iter__(self) -> peekable[_T]: ... + def __bool__(self) -> bool: ... + @overload + def peek(self) -> _T: ... + @overload + def peek(self, default: _U) -> Union[_T, _U]: ... + def prepend(self, *items: _T) -> None: ... + def __next__(self) -> _T: ... + @overload + def __getitem__(self, index: int) -> _T: ... + @overload + def __getitem__(self, index: slice) -> List[_T]: ... + +def consumer(func: _GenFn) -> _GenFn: ... +def ilen(iterable: Iterable[object]) -> int: ... +def iterate(func: Callable[[_T], _T], start: _T) -> Iterator[_T]: ... +def with_iter( + context_manager: ContextManager[Iterable[_T]], +) -> Iterator[_T]: ... +def one( + iterable: Iterable[_T], + too_short: Optional[_Raisable] = ..., + too_long: Optional[_Raisable] = ..., +) -> _T: ... +def raise_(exception: _Raisable, *args: Any) -> None: ... +def strictly_n( + iterable: Iterable[_T], + n: int, + too_short: Optional[_GenFn] = ..., + too_long: Optional[_GenFn] = ..., +) -> List[_T]: ... +def distinct_permutations( + iterable: Iterable[_T], r: Optional[int] = ... +) -> Iterator[Tuple[_T, ...]]: ... +def intersperse( + e: _U, iterable: Iterable[_T], n: int = ... +) -> Iterator[Union[_T, _U]]: ... +def unique_to_each(*iterables: Iterable[_T]) -> List[List[_T]]: ... +@overload +def windowed( + seq: Iterable[_T], n: int, *, step: int = ... +) -> Iterator[Tuple[Optional[_T], ...]]: ... +@overload +def windowed( + seq: Iterable[_T], n: int, fillvalue: _U, step: int = ... +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... +def substrings(iterable: Iterable[_T]) -> Iterator[Tuple[_T, ...]]: ... +def substrings_indexes( + seq: Sequence[_T], reverse: bool = ... +) -> Iterator[Tuple[Sequence[_T], int, int]]: ... + +class bucket(Generic[_T, _U], Container[_U]): + def __init__( + self, + iterable: Iterable[_T], + key: Callable[[_T], _U], + validator: Optional[Callable[[object], object]] = ..., + ) -> None: ... + def __contains__(self, value: object) -> bool: ... + def __iter__(self) -> Iterator[_U]: ... + def __getitem__(self, value: object) -> Iterator[_T]: ... + +def spy( + iterable: Iterable[_T], n: int = ... +) -> Tuple[List[_T], Iterator[_T]]: ... +def interleave(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def interleave_longest(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def interleave_evenly( + iterables: List[Iterable[_T]], lengths: Optional[List[int]] = ... +) -> Iterator[_T]: ... +def collapse( + iterable: Iterable[Any], + base_type: Optional[type] = ..., + levels: Optional[int] = ..., +) -> Iterator[Any]: ... +@overload +def side_effect( + func: Callable[[_T], object], + iterable: Iterable[_T], + chunk_size: None = ..., + before: Optional[Callable[[], object]] = ..., + after: Optional[Callable[[], object]] = ..., +) -> Iterator[_T]: ... +@overload +def side_effect( + func: Callable[[List[_T]], object], + iterable: Iterable[_T], + chunk_size: int, + before: Optional[Callable[[], object]] = ..., + after: Optional[Callable[[], object]] = ..., +) -> Iterator[_T]: ... +def sliced( + seq: Sequence[_T], n: int, strict: bool = ... +) -> Iterator[Sequence[_T]]: ... +def split_at( + iterable: Iterable[_T], + pred: Callable[[_T], object], + maxsplit: int = ..., + keep_separator: bool = ..., +) -> Iterator[List[_T]]: ... +def split_before( + iterable: Iterable[_T], pred: Callable[[_T], object], maxsplit: int = ... +) -> Iterator[List[_T]]: ... +def split_after( + iterable: Iterable[_T], pred: Callable[[_T], object], maxsplit: int = ... +) -> Iterator[List[_T]]: ... +def split_when( + iterable: Iterable[_T], + pred: Callable[[_T, _T], object], + maxsplit: int = ..., +) -> Iterator[List[_T]]: ... +def split_into( + iterable: Iterable[_T], sizes: Iterable[Optional[int]] +) -> Iterator[List[_T]]: ... +@overload +def padded( + iterable: Iterable[_T], + *, + n: Optional[int] = ..., + next_multiple: bool = ..., +) -> Iterator[Optional[_T]]: ... +@overload +def padded( + iterable: Iterable[_T], + fillvalue: _U, + n: Optional[int] = ..., + next_multiple: bool = ..., +) -> Iterator[Union[_T, _U]]: ... +@overload +def repeat_last(iterable: Iterable[_T]) -> Iterator[_T]: ... +@overload +def repeat_last( + iterable: Iterable[_T], default: _U +) -> Iterator[Union[_T, _U]]: ... +def distribute(n: int, iterable: Iterable[_T]) -> List[Iterator[_T]]: ... +@overload +def stagger( + iterable: Iterable[_T], + offsets: _SizedIterable[int] = ..., + longest: bool = ..., +) -> Iterator[Tuple[Optional[_T], ...]]: ... +@overload +def stagger( + iterable: Iterable[_T], + offsets: _SizedIterable[int] = ..., + longest: bool = ..., + fillvalue: _U = ..., +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... + +class UnequalIterablesError(ValueError): + def __init__( + self, details: Optional[Tuple[int, int, int]] = ... + ) -> None: ... + +@overload +def zip_equal(__iter1: Iterable[_T1]) -> Iterator[Tuple[_T1]]: ... +@overload +def zip_equal( + __iter1: Iterable[_T1], __iter2: Iterable[_T2] +) -> Iterator[Tuple[_T1, _T2]]: ... +@overload +def zip_equal( + __iter1: Iterable[_T], + __iter2: Iterable[_T], + __iter3: Iterable[_T], + *iterables: Iterable[_T], +) -> Iterator[Tuple[_T, ...]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T1], + *, + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: None = None, +) -> Iterator[Tuple[Optional[_T1]]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T1], + __iter2: Iterable[_T2], + *, + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: None = None, +) -> Iterator[Tuple[Optional[_T1], Optional[_T2]]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T], + __iter2: Iterable[_T], + __iter3: Iterable[_T], + *iterables: Iterable[_T], + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: None = None, +) -> Iterator[Tuple[Optional[_T], ...]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T1], + *, + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: _U, +) -> Iterator[Tuple[Union[_T1, _U]]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T1], + __iter2: Iterable[_T2], + *, + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: _U, +) -> Iterator[Tuple[Union[_T1, _U], Union[_T2, _U]]]: ... +@overload +def zip_offset( + __iter1: Iterable[_T], + __iter2: Iterable[_T], + __iter3: Iterable[_T], + *iterables: Iterable[_T], + offsets: _SizedIterable[int], + longest: bool = ..., + fillvalue: _U, +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... +def sort_together( + iterables: Iterable[Iterable[_T]], + key_list: Iterable[int] = ..., + key: Optional[Callable[..., Any]] = ..., + reverse: bool = ..., +) -> List[Tuple[_T, ...]]: ... +def unzip(iterable: Iterable[Sequence[_T]]) -> Tuple[Iterator[_T], ...]: ... +def divide(n: int, iterable: Iterable[_T]) -> List[Iterator[_T]]: ... +def always_iterable( + obj: object, + base_type: Union[ + type, Tuple[Union[type, Tuple[Any, ...]], ...], None + ] = ..., +) -> Iterator[Any]: ... +def adjacent( + predicate: Callable[[_T], bool], + iterable: Iterable[_T], + distance: int = ..., +) -> Iterator[Tuple[bool, _T]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: None = None, + valuefunc: None = None, + reducefunc: None = None, +) -> Iterator[Tuple[_T, Iterator[_T]]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None, + reducefunc: None, +) -> Iterator[Tuple[_U, Iterator[_T]]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: None, + valuefunc: Callable[[_T], _V], + reducefunc: None, +) -> Iterable[Tuple[_T, Iterable[_V]]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: None, +) -> Iterable[Tuple[_U, Iterator[_V]]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: None, + valuefunc: None, + reducefunc: Callable[[Iterator[_T]], _W], +) -> Iterable[Tuple[_T, _W]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None, + reducefunc: Callable[[Iterator[_T]], _W], +) -> Iterable[Tuple[_U, _W]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: None, + valuefunc: Callable[[_T], _V], + reducefunc: Callable[[Iterable[_V]], _W], +) -> Iterable[Tuple[_T, _W]]: ... +@overload +def groupby_transform( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: Callable[[Iterable[_V]], _W], +) -> Iterable[Tuple[_U, _W]]: ... + +class numeric_range(Generic[_T, _U], Sequence[_T], Hashable, Reversible[_T]): + @overload + def __init__(self, __stop: _T) -> None: ... + @overload + def __init__(self, __start: _T, __stop: _T) -> None: ... + @overload + def __init__(self, __start: _T, __stop: _T, __step: _U) -> None: ... + def __bool__(self) -> bool: ... + def __contains__(self, elem: object) -> bool: ... + def __eq__(self, other: object) -> bool: ... + @overload + def __getitem__(self, key: int) -> _T: ... + @overload + def __getitem__(self, key: slice) -> numeric_range[_T, _U]: ... + def __hash__(self) -> int: ... + def __iter__(self) -> Iterator[_T]: ... + def __len__(self) -> int: ... + def __reduce__( + self, + ) -> Tuple[Type[numeric_range[_T, _U]], Tuple[_T, _T, _U]]: ... + def __repr__(self) -> str: ... + def __reversed__(self) -> Iterator[_T]: ... + def count(self, value: _T) -> int: ... + def index(self, value: _T) -> int: ... # type: ignore + +def count_cycle( + iterable: Iterable[_T], n: Optional[int] = ... +) -> Iterable[Tuple[int, _T]]: ... +def mark_ends( + iterable: Iterable[_T], +) -> Iterable[Tuple[bool, bool, _T]]: ... +def locate( + iterable: Iterable[object], + pred: Callable[..., Any] = ..., + window_size: Optional[int] = ..., +) -> Iterator[int]: ... +def lstrip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... +def rstrip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... +def strip( + iterable: Iterable[_T], pred: Callable[[_T], object] +) -> Iterator[_T]: ... + +class islice_extended(Generic[_T], Iterator[_T]): + def __init__( + self, iterable: Iterable[_T], *args: Optional[int] + ) -> None: ... + def __iter__(self) -> islice_extended[_T]: ... + def __next__(self) -> _T: ... + def __getitem__(self, index: slice) -> islice_extended[_T]: ... + +def always_reversible(iterable: Iterable[_T]) -> Iterator[_T]: ... +def consecutive_groups( + iterable: Iterable[_T], ordering: Callable[[_T], int] = ... +) -> Iterator[Iterator[_T]]: ... +@overload +def difference( + iterable: Iterable[_T], + func: Callable[[_T, _T], _U] = ..., + *, + initial: None = ..., +) -> Iterator[Union[_T, _U]]: ... +@overload +def difference( + iterable: Iterable[_T], func: Callable[[_T, _T], _U] = ..., *, initial: _U +) -> Iterator[_U]: ... + +class SequenceView(Generic[_T], Sequence[_T]): + def __init__(self, target: Sequence[_T]) -> None: ... + @overload + def __getitem__(self, index: int) -> _T: ... + @overload + def __getitem__(self, index: slice) -> Sequence[_T]: ... + def __len__(self) -> int: ... + +class seekable(Generic[_T], Iterator[_T]): + def __init__( + self, iterable: Iterable[_T], maxlen: Optional[int] = ... + ) -> None: ... + def __iter__(self) -> seekable[_T]: ... + def __next__(self) -> _T: ... + def __bool__(self) -> bool: ... + @overload + def peek(self) -> _T: ... + @overload + def peek(self, default: _U) -> Union[_T, _U]: ... + def elements(self) -> SequenceView[_T]: ... + def seek(self, index: int) -> None: ... + +class run_length: + @staticmethod + def encode(iterable: Iterable[_T]) -> Iterator[Tuple[_T, int]]: ... + @staticmethod + def decode(iterable: Iterable[Tuple[_T, int]]) -> Iterator[_T]: ... + +def exactly_n( + iterable: Iterable[_T], n: int, predicate: Callable[[_T], object] = ... +) -> bool: ... +def circular_shifts(iterable: Iterable[_T]) -> List[Tuple[_T, ...]]: ... +def make_decorator( + wrapping_func: Callable[..., _U], result_index: int = ... +) -> Callable[..., Callable[[Callable[..., Any]], Callable[..., _U]]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None = ..., + reducefunc: None = ..., +) -> Dict[_U, List[_T]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: None = ..., +) -> Dict[_U, List[_V]]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: None = ..., + reducefunc: Callable[[List[_T]], _W] = ..., +) -> Dict[_U, _W]: ... +@overload +def map_reduce( + iterable: Iterable[_T], + keyfunc: Callable[[_T], _U], + valuefunc: Callable[[_T], _V], + reducefunc: Callable[[List[_V]], _W], +) -> Dict[_U, _W]: ... +def rlocate( + iterable: Iterable[_T], + pred: Callable[..., object] = ..., + window_size: Optional[int] = ..., +) -> Iterator[int]: ... +def replace( + iterable: Iterable[_T], + pred: Callable[..., object], + substitutes: Iterable[_U], + count: Optional[int] = ..., + window_size: int = ..., +) -> Iterator[Union[_T, _U]]: ... +def partitions(iterable: Iterable[_T]) -> Iterator[List[List[_T]]]: ... +def set_partitions( + iterable: Iterable[_T], k: Optional[int] = ... +) -> Iterator[List[List[_T]]]: ... + +class time_limited(Generic[_T], Iterator[_T]): + def __init__( + self, limit_seconds: float, iterable: Iterable[_T] + ) -> None: ... + def __iter__(self) -> islice_extended[_T]: ... + def __next__(self) -> _T: ... + +@overload +def only( + iterable: Iterable[_T], *, too_long: Optional[_Raisable] = ... +) -> Optional[_T]: ... +@overload +def only( + iterable: Iterable[_T], default: _U, too_long: Optional[_Raisable] = ... +) -> Union[_T, _U]: ... +def ichunked(iterable: Iterable[_T], n: int) -> Iterator[Iterator[_T]]: ... +def distinct_combinations( + iterable: Iterable[_T], r: int +) -> Iterator[Tuple[_T, ...]]: ... +def filter_except( + validator: Callable[[Any], object], + iterable: Iterable[_T], + *exceptions: Type[BaseException], +) -> Iterator[_T]: ... +def map_except( + function: Callable[[Any], _U], + iterable: Iterable[_T], + *exceptions: Type[BaseException], +) -> Iterator[_U]: ... +def map_if( + iterable: Iterable[Any], + pred: Callable[[Any], bool], + func: Callable[[Any], Any], + func_else: Optional[Callable[[Any], Any]] = ..., +) -> Iterator[Any]: ... +def sample( + iterable: Iterable[_T], + k: int, + weights: Optional[Iterable[float]] = ..., +) -> List[_T]: ... +def is_sorted( + iterable: Iterable[_T], + key: Optional[Callable[[_T], _U]] = ..., + reverse: bool = False, + strict: bool = False, +) -> bool: ... + +class AbortThread(BaseException): + pass + +class callback_iter(Generic[_T], Iterator[_T]): + def __init__( + self, + func: Callable[..., Any], + callback_kwd: str = ..., + wait_seconds: float = ..., + ) -> None: ... + def __enter__(self) -> callback_iter[_T]: ... + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> Optional[bool]: ... + def __iter__(self) -> callback_iter[_T]: ... + def __next__(self) -> _T: ... + def _reader(self) -> Iterator[_T]: ... + @property + def done(self) -> bool: ... + @property + def result(self) -> Any: ... + +def windowed_complete( + iterable: Iterable[_T], n: int +) -> Iterator[Tuple[_T, ...]]: ... +def all_unique( + iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = ... +) -> bool: ... +def nth_product(index: int, *args: Iterable[_T]) -> Tuple[_T, ...]: ... +def nth_permutation( + iterable: Iterable[_T], r: int, index: int +) -> Tuple[_T, ...]: ... +def value_chain(*args: Union[_T, Iterable[_T]]) -> Iterable[_T]: ... +def product_index(element: Iterable[_T], *args: Iterable[_T]) -> int: ... +def combination_index( + element: Iterable[_T], iterable: Iterable[_T] +) -> int: ... +def permutation_index( + element: Iterable[_T], iterable: Iterable[_T] +) -> int: ... +def repeat_each(iterable: Iterable[_T], n: int = ...) -> Iterator[_T]: ... + +class countable(Generic[_T], Iterator[_T]): + def __init__(self, iterable: Iterable[_T]) -> None: ... + def __iter__(self) -> countable[_T]: ... + def __next__(self) -> _T: ... + +def chunked_even(iterable: Iterable[_T], n: int) -> Iterator[List[_T]]: ... +def zip_broadcast( + *objects: Union[_T, Iterable[_T]], + scalar_types: Union[ + type, Tuple[Union[type, Tuple[Any, ...]], ...], None + ] = ..., + strict: bool = ..., +) -> Iterable[Tuple[_T, ...]]: ... +def unique_in_window( + iterable: Iterable[_T], n: int, key: Optional[Callable[[_T], _U]] = ... +) -> Iterator[_T]: ... +def duplicates_everseen( + iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = ... +) -> Iterator[_T]: ... +def duplicates_justseen( + iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = ... +) -> Iterator[_T]: ... + +class _SupportsLessThan(Protocol): + def __lt__(self, __other: Any) -> bool: ... + +_SupportsLessThanT = TypeVar("_SupportsLessThanT", bound=_SupportsLessThan) + +@overload +def minmax( + iterable_or_value: Iterable[_SupportsLessThanT], *, key: None = None +) -> Tuple[_SupportsLessThanT, _SupportsLessThanT]: ... +@overload +def minmax( + iterable_or_value: Iterable[_T], *, key: Callable[[_T], _SupportsLessThan] +) -> Tuple[_T, _T]: ... +@overload +def minmax( + iterable_or_value: Iterable[_SupportsLessThanT], + *, + key: None = None, + default: _U, +) -> Union[_U, Tuple[_SupportsLessThanT, _SupportsLessThanT]]: ... +@overload +def minmax( + iterable_or_value: Iterable[_T], + *, + key: Callable[[_T], _SupportsLessThan], + default: _U, +) -> Union[_U, Tuple[_T, _T]]: ... +@overload +def minmax( + iterable_or_value: _SupportsLessThanT, + __other: _SupportsLessThanT, + *others: _SupportsLessThanT, +) -> Tuple[_SupportsLessThanT, _SupportsLessThanT]: ... +@overload +def minmax( + iterable_or_value: _T, + __other: _T, + *others: _T, + key: Callable[[_T], _SupportsLessThan], +) -> Tuple[_T, _T]: ... +def longest_common_prefix( + iterables: Iterable[Iterable[_T]], +) -> Iterator[_T]: ... +def iequals(*iterables: Iterable[object]) -> bool: ... +def constrained_batches( + iterable: Iterable[object], + max_size: int, + max_count: Optional[int] = ..., + get_len: Callable[[_T], object] = ..., + strict: bool = ..., +) -> Iterator[Tuple[_T]]: ... diff --git a/libs/common/more_itertools/py.typed b/libs/common/more_itertools/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/libs/common/more_itertools/recipes.py b/libs/common/more_itertools/recipes.py new file mode 100644 index 00000000..85796207 --- /dev/null +++ b/libs/common/more_itertools/recipes.py @@ -0,0 +1,841 @@ +"""Imported from the recipes section of the itertools documentation. + +All functions taken from the recipes section of the itertools library docs +[1]_. +Some backward-compatible usability improvements have been made. + +.. [1] http://docs.python.org/library/itertools.html#recipes + +""" +import math +import operator + +from collections import deque +from collections.abc import Sized +from functools import reduce +from itertools import ( + chain, + combinations, + compress, + count, + cycle, + groupby, + islice, + repeat, + starmap, + tee, + zip_longest, +) +from random import randrange, sample, choice + +__all__ = [ + 'all_equal', + 'batched', + 'before_and_after', + 'consume', + 'convolve', + 'dotproduct', + 'first_true', + 'flatten', + 'grouper', + 'iter_except', + 'ncycles', + 'nth', + 'nth_combination', + 'padnone', + 'pad_none', + 'pairwise', + 'partition', + 'polynomial_from_roots', + 'powerset', + 'prepend', + 'quantify', + 'random_combination_with_replacement', + 'random_combination', + 'random_permutation', + 'random_product', + 'repeatfunc', + 'roundrobin', + 'sieve', + 'sliding_window', + 'subslices', + 'tabulate', + 'tail', + 'take', + 'triplewise', + 'unique_everseen', + 'unique_justseen', +] + +_marker = object() + + +def take(n, iterable): + """Return first *n* items of the iterable as a list. + + >>> take(3, range(10)) + [0, 1, 2] + + If there are fewer than *n* items in the iterable, all of them are + returned. + + >>> take(10, range(3)) + [0, 1, 2] + + """ + return list(islice(iterable, n)) + + +def tabulate(function, start=0): + """Return an iterator over the results of ``func(start)``, + ``func(start + 1)``, ``func(start + 2)``... + + *func* should be a function that accepts one integer argument. + + If *start* is not specified it defaults to 0. It will be incremented each + time the iterator is advanced. + + >>> square = lambda x: x ** 2 + >>> iterator = tabulate(square, -3) + >>> take(4, iterator) + [9, 4, 1, 0] + + """ + return map(function, count(start)) + + +def tail(n, iterable): + """Return an iterator over the last *n* items of *iterable*. + + >>> t = tail(3, 'ABCDEFG') + >>> list(t) + ['E', 'F', 'G'] + + """ + # If the given iterable has a length, then we can use islice to get its + # final elements. Note that if the iterable is not actually Iterable, + # either islice or deque will throw a TypeError. This is why we don't + # check if it is Iterable. + if isinstance(iterable, Sized): + yield from islice(iterable, max(0, len(iterable) - n), None) + else: + yield from iter(deque(iterable, maxlen=n)) + + +def consume(iterator, n=None): + """Advance *iterable* by *n* steps. If *n* is ``None``, consume it + entirely. + + Efficiently exhausts an iterator without returning values. Defaults to + consuming the whole iterator, but an optional second argument may be + provided to limit consumption. + + >>> i = (x for x in range(10)) + >>> next(i) + 0 + >>> consume(i, 3) + >>> next(i) + 4 + >>> consume(i) + >>> next(i) + Traceback (most recent call last): + File "", line 1, in + StopIteration + + If the iterator has fewer items remaining than the provided limit, the + whole iterator will be consumed. + + >>> i = (x for x in range(3)) + >>> consume(i, 5) + >>> next(i) + Traceback (most recent call last): + File "", line 1, in + StopIteration + + """ + # Use functions that consume iterators at C speed. + if n is None: + # feed the entire iterator into a zero-length deque + deque(iterator, maxlen=0) + else: + # advance to the empty slice starting at position n + next(islice(iterator, n, n), None) + + +def nth(iterable, n, default=None): + """Returns the nth item or a default value. + + >>> l = range(10) + >>> nth(l, 3) + 3 + >>> nth(l, 20, "zebra") + 'zebra' + + """ + return next(islice(iterable, n, None), default) + + +def all_equal(iterable): + """ + Returns ``True`` if all the elements are equal to each other. + + >>> all_equal('aaaa') + True + >>> all_equal('aaab') + False + + """ + g = groupby(iterable) + return next(g, True) and not next(g, False) + + +def quantify(iterable, pred=bool): + """Return the how many times the predicate is true. + + >>> quantify([True, False, True]) + 2 + + """ + return sum(map(pred, iterable)) + + +def pad_none(iterable): + """Returns the sequence of elements and then returns ``None`` indefinitely. + + >>> take(5, pad_none(range(3))) + [0, 1, 2, None, None] + + Useful for emulating the behavior of the built-in :func:`map` function. + + See also :func:`padded`. + + """ + return chain(iterable, repeat(None)) + + +padnone = pad_none + + +def ncycles(iterable, n): + """Returns the sequence elements *n* times + + >>> list(ncycles(["a", "b"], 3)) + ['a', 'b', 'a', 'b', 'a', 'b'] + + """ + return chain.from_iterable(repeat(tuple(iterable), n)) + + +def dotproduct(vec1, vec2): + """Returns the dot product of the two iterables. + + >>> dotproduct([10, 10], [20, 20]) + 400 + + """ + return sum(map(operator.mul, vec1, vec2)) + + +def flatten(listOfLists): + """Return an iterator flattening one level of nesting in a list of lists. + + >>> list(flatten([[0, 1], [2, 3]])) + [0, 1, 2, 3] + + See also :func:`collapse`, which can flatten multiple levels of nesting. + + """ + return chain.from_iterable(listOfLists) + + +def repeatfunc(func, times=None, *args): + """Call *func* with *args* repeatedly, returning an iterable over the + results. + + If *times* is specified, the iterable will terminate after that many + repetitions: + + >>> from operator import add + >>> times = 4 + >>> args = 3, 5 + >>> list(repeatfunc(add, times, *args)) + [8, 8, 8, 8] + + If *times* is ``None`` the iterable will not terminate: + + >>> from random import randrange + >>> times = None + >>> args = 1, 11 + >>> take(6, repeatfunc(randrange, times, *args)) # doctest:+SKIP + [2, 4, 8, 1, 8, 4] + + """ + if times is None: + return starmap(func, repeat(args)) + return starmap(func, repeat(args, times)) + + +def _pairwise(iterable): + """Returns an iterator of paired items, overlapping, from the original + + >>> take(4, pairwise(count())) + [(0, 1), (1, 2), (2, 3), (3, 4)] + + On Python 3.10 and above, this is an alias for :func:`itertools.pairwise`. + + """ + a, b = tee(iterable) + next(b, None) + yield from zip(a, b) + + +try: + from itertools import pairwise as itertools_pairwise +except ImportError: + pairwise = _pairwise +else: + + def pairwise(iterable): + yield from itertools_pairwise(iterable) + + pairwise.__doc__ = _pairwise.__doc__ + + +class UnequalIterablesError(ValueError): + def __init__(self, details=None): + msg = 'Iterables have different lengths' + if details is not None: + msg += (': index 0 has length {}; index {} has length {}').format( + *details + ) + + super().__init__(msg) + + +def _zip_equal_generator(iterables): + for combo in zip_longest(*iterables, fillvalue=_marker): + for val in combo: + if val is _marker: + raise UnequalIterablesError() + yield combo + + +def _zip_equal(*iterables): + # Check whether the iterables are all the same size. + try: + first_size = len(iterables[0]) + for i, it in enumerate(iterables[1:], 1): + size = len(it) + if size != first_size: + break + else: + # If we didn't break out, we can use the built-in zip. + return zip(*iterables) + + # If we did break out, there was a mismatch. + raise UnequalIterablesError(details=(first_size, i, size)) + # If any one of the iterables didn't have a length, start reading + # them until one runs out. + except TypeError: + return _zip_equal_generator(iterables) + + +def grouper(iterable, n, incomplete='fill', fillvalue=None): + """Group elements from *iterable* into fixed-length groups of length *n*. + + >>> list(grouper('ABCDEF', 3)) + [('A', 'B', 'C'), ('D', 'E', 'F')] + + The keyword arguments *incomplete* and *fillvalue* control what happens for + iterables whose length is not a multiple of *n*. + + When *incomplete* is `'fill'`, the last group will contain instances of + *fillvalue*. + + >>> list(grouper('ABCDEFG', 3, incomplete='fill', fillvalue='x')) + [('A', 'B', 'C'), ('D', 'E', 'F'), ('G', 'x', 'x')] + + When *incomplete* is `'ignore'`, the last group will not be emitted. + + >>> list(grouper('ABCDEFG', 3, incomplete='ignore', fillvalue='x')) + [('A', 'B', 'C'), ('D', 'E', 'F')] + + When *incomplete* is `'strict'`, a subclass of `ValueError` will be raised. + + >>> it = grouper('ABCDEFG', 3, incomplete='strict') + >>> list(it) # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + UnequalIterablesError + + """ + args = [iter(iterable)] * n + if incomplete == 'fill': + return zip_longest(*args, fillvalue=fillvalue) + if incomplete == 'strict': + return _zip_equal(*args) + if incomplete == 'ignore': + return zip(*args) + else: + raise ValueError('Expected fill, strict, or ignore') + + +def roundrobin(*iterables): + """Yields an item from each iterable, alternating between them. + + >>> list(roundrobin('ABC', 'D', 'EF')) + ['A', 'D', 'E', 'B', 'F', 'C'] + + This function produces the same output as :func:`interleave_longest`, but + may perform better for some inputs (in particular when the number of + iterables is small). + + """ + # Recipe credited to George Sakkis + pending = len(iterables) + nexts = cycle(iter(it).__next__ for it in iterables) + while pending: + try: + for next in nexts: + yield next() + except StopIteration: + pending -= 1 + nexts = cycle(islice(nexts, pending)) + + +def partition(pred, iterable): + """ + Returns a 2-tuple of iterables derived from the input iterable. + The first yields the items that have ``pred(item) == False``. + The second yields the items that have ``pred(item) == True``. + + >>> is_odd = lambda x: x % 2 != 0 + >>> iterable = range(10) + >>> even_items, odd_items = partition(is_odd, iterable) + >>> list(even_items), list(odd_items) + ([0, 2, 4, 6, 8], [1, 3, 5, 7, 9]) + + If *pred* is None, :func:`bool` is used. + + >>> iterable = [0, 1, False, True, '', ' '] + >>> false_items, true_items = partition(None, iterable) + >>> list(false_items), list(true_items) + ([0, False, ''], [1, True, ' ']) + + """ + if pred is None: + pred = bool + + evaluations = ((pred(x), x) for x in iterable) + t1, t2 = tee(evaluations) + return ( + (x for (cond, x) in t1 if not cond), + (x for (cond, x) in t2 if cond), + ) + + +def powerset(iterable): + """Yields all possible subsets of the iterable. + + >>> list(powerset([1, 2, 3])) + [(), (1,), (2,), (3,), (1, 2), (1, 3), (2, 3), (1, 2, 3)] + + :func:`powerset` will operate on iterables that aren't :class:`set` + instances, so repeated elements in the input will produce repeated elements + in the output. Use :func:`unique_everseen` on the input to avoid generating + duplicates: + + >>> seq = [1, 1, 0] + >>> list(powerset(seq)) + [(), (1,), (1,), (0,), (1, 1), (1, 0), (1, 0), (1, 1, 0)] + >>> from more_itertools import unique_everseen + >>> list(powerset(unique_everseen(seq))) + [(), (1,), (0,), (1, 0)] + + """ + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s) + 1)) + + +def unique_everseen(iterable, key=None): + """ + Yield unique elements, preserving order. + + >>> list(unique_everseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D'] + >>> list(unique_everseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'D'] + + Sequences with a mix of hashable and unhashable items can be used. + The function will be slower (i.e., `O(n^2)`) for unhashable items. + + Remember that ``list`` objects are unhashable - you can use the *key* + parameter to transform the list to a tuple (which is hashable) to + avoid a slowdown. + + >>> iterable = ([1, 2], [2, 3], [1, 2]) + >>> list(unique_everseen(iterable)) # Slow + [[1, 2], [2, 3]] + >>> list(unique_everseen(iterable, key=tuple)) # Faster + [[1, 2], [2, 3]] + + Similary, you may want to convert unhashable ``set`` objects with + ``key=frozenset``. For ``dict`` objects, + ``key=lambda x: frozenset(x.items())`` can be used. + + """ + seenset = set() + seenset_add = seenset.add + seenlist = [] + seenlist_add = seenlist.append + use_key = key is not None + + for element in iterable: + k = key(element) if use_key else element + try: + if k not in seenset: + seenset_add(k) + yield element + except TypeError: + if k not in seenlist: + seenlist_add(k) + yield element + + +def unique_justseen(iterable, key=None): + """Yields elements in order, ignoring serial duplicates + + >>> list(unique_justseen('AAAABBBCCDAABBB')) + ['A', 'B', 'C', 'D', 'A', 'B'] + >>> list(unique_justseen('ABBCcAD', str.lower)) + ['A', 'B', 'C', 'A', 'D'] + + """ + return map(next, map(operator.itemgetter(1), groupby(iterable, key))) + + +def iter_except(func, exception, first=None): + """Yields results from a function repeatedly until an exception is raised. + + Converts a call-until-exception interface to an iterator interface. + Like ``iter(func, sentinel)``, but uses an exception instead of a sentinel + to end the loop. + + >>> l = [0, 1, 2] + >>> list(iter_except(l.pop, IndexError)) + [2, 1, 0] + + Multiple exceptions can be specified as a stopping condition: + + >>> l = [1, 2, 3, '...', 4, 5, 6] + >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError))) + [7, 6, 5] + >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError))) + [4, 3, 2] + >>> list(iter_except(lambda: 1 + l.pop(), (IndexError, TypeError))) + [] + + """ + try: + if first is not None: + yield first() + while 1: + yield func() + except exception: + pass + + +def first_true(iterable, default=None, pred=None): + """ + Returns the first true value in the iterable. + + If no true value is found, returns *default* + + If *pred* is not None, returns the first item for which + ``pred(item) == True`` . + + >>> first_true(range(10)) + 1 + >>> first_true(range(10), pred=lambda x: x > 5) + 6 + >>> first_true(range(10), default='missing', pred=lambda x: x > 9) + 'missing' + + """ + return next(filter(pred, iterable), default) + + +def random_product(*args, repeat=1): + """Draw an item at random from each of the input iterables. + + >>> random_product('abc', range(4), 'XYZ') # doctest:+SKIP + ('c', 3, 'Z') + + If *repeat* is provided as a keyword argument, that many items will be + drawn from each iterable. + + >>> random_product('abcd', range(4), repeat=2) # doctest:+SKIP + ('a', 2, 'd', 3) + + This equivalent to taking a random selection from + ``itertools.product(*args, **kwarg)``. + + """ + pools = [tuple(pool) for pool in args] * repeat + return tuple(choice(pool) for pool in pools) + + +def random_permutation(iterable, r=None): + """Return a random *r* length permutation of the elements in *iterable*. + + If *r* is not specified or is ``None``, then *r* defaults to the length of + *iterable*. + + >>> random_permutation(range(5)) # doctest:+SKIP + (3, 4, 0, 1, 2) + + This equivalent to taking a random selection from + ``itertools.permutations(iterable, r)``. + + """ + pool = tuple(iterable) + r = len(pool) if r is None else r + return tuple(sample(pool, r)) + + +def random_combination(iterable, r): + """Return a random *r* length subsequence of the elements in *iterable*. + + >>> random_combination(range(5), 3) # doctest:+SKIP + (2, 3, 4) + + This equivalent to taking a random selection from + ``itertools.combinations(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(sample(range(n), r)) + return tuple(pool[i] for i in indices) + + +def random_combination_with_replacement(iterable, r): + """Return a random *r* length subsequence of elements in *iterable*, + allowing individual elements to be repeated. + + >>> random_combination_with_replacement(range(3), 5) # doctest:+SKIP + (0, 0, 1, 2, 2) + + This equivalent to taking a random selection from + ``itertools.combinations_with_replacement(iterable, r)``. + + """ + pool = tuple(iterable) + n = len(pool) + indices = sorted(randrange(n) for i in range(r)) + return tuple(pool[i] for i in indices) + + +def nth_combination(iterable, r, index): + """Equivalent to ``list(combinations(iterable, r))[index]``. + + The subsequences of *iterable* that are of length *r* can be ordered + lexicographically. :func:`nth_combination` computes the subsequence at + sort position *index* directly, without computing the previous + subsequences. + + >>> nth_combination(range(5), 3, 5) + (0, 3, 4) + + ``ValueError`` will be raised If *r* is negative or greater than the length + of *iterable*. + ``IndexError`` will be raised if the given *index* is invalid. + """ + pool = tuple(iterable) + n = len(pool) + if (r < 0) or (r > n): + raise ValueError + + c = 1 + k = min(r, n - r) + for i in range(1, k + 1): + c = c * (n - k + i) // i + + if index < 0: + index += c + + if (index < 0) or (index >= c): + raise IndexError + + result = [] + while r: + c, n, r = c * r // n, n - 1, r - 1 + while index >= c: + index -= c + c, n = c * (n - r) // n, n - 1 + result.append(pool[-1 - n]) + + return tuple(result) + + +def prepend(value, iterator): + """Yield *value*, followed by the elements in *iterator*. + + >>> value = '0' + >>> iterator = ['1', '2', '3'] + >>> list(prepend(value, iterator)) + ['0', '1', '2', '3'] + + To prepend multiple values, see :func:`itertools.chain` + or :func:`value_chain`. + + """ + return chain([value], iterator) + + +def convolve(signal, kernel): + """Convolve the iterable *signal* with the iterable *kernel*. + + >>> signal = (1, 2, 3, 4, 5) + >>> kernel = [3, 2, 1] + >>> list(convolve(signal, kernel)) + [3, 8, 14, 20, 26, 14, 5] + + Note: the input arguments are not interchangeable, as the *kernel* + is immediately consumed and stored. + + """ + kernel = tuple(kernel)[::-1] + n = len(kernel) + window = deque([0], maxlen=n) * n + for x in chain(signal, repeat(0, n - 1)): + window.append(x) + yield sum(map(operator.mul, kernel, window)) + + +def before_and_after(predicate, it): + """A variant of :func:`takewhile` that allows complete access to the + remainder of the iterator. + + >>> it = iter('ABCdEfGhI') + >>> all_upper, remainder = before_and_after(str.isupper, it) + >>> ''.join(all_upper) + 'ABC' + >>> ''.join(remainder) # takewhile() would lose the 'd' + 'dEfGhI' + + Note that the first iterator must be fully consumed before the second + iterator can generate valid results. + """ + it = iter(it) + transition = [] + + def true_iterator(): + for elem in it: + if predicate(elem): + yield elem + else: + transition.append(elem) + return + + # Note: this is different from itertools recipes to allow nesting + # before_and_after remainders into before_and_after again. See tests + # for an example. + remainder_iterator = chain(transition, it) + + return true_iterator(), remainder_iterator + + +def triplewise(iterable): + """Return overlapping triplets from *iterable*. + + >>> list(triplewise('ABCDE')) + [('A', 'B', 'C'), ('B', 'C', 'D'), ('C', 'D', 'E')] + + """ + for (a, _), (b, c) in pairwise(pairwise(iterable)): + yield a, b, c + + +def sliding_window(iterable, n): + """Return a sliding window of width *n* over *iterable*. + + >>> list(sliding_window(range(6), 4)) + [(0, 1, 2, 3), (1, 2, 3, 4), (2, 3, 4, 5)] + + If *iterable* has fewer than *n* items, then nothing is yielded: + + >>> list(sliding_window(range(3), 4)) + [] + + For a variant with more features, see :func:`windowed`. + """ + it = iter(iterable) + window = deque(islice(it, n), maxlen=n) + if len(window) == n: + yield tuple(window) + for x in it: + window.append(x) + yield tuple(window) + + +def subslices(iterable): + """Return all contiguous non-empty subslices of *iterable*. + + >>> list(subslices('ABC')) + [['A'], ['A', 'B'], ['A', 'B', 'C'], ['B'], ['B', 'C'], ['C']] + + This is similar to :func:`substrings`, but emits items in a different + order. + """ + seq = list(iterable) + slices = starmap(slice, combinations(range(len(seq) + 1), 2)) + return map(operator.getitem, repeat(seq), slices) + + +def polynomial_from_roots(roots): + """Compute a polynomial's coefficients from its roots. + + >>> roots = [5, -4, 3] # (x - 5) * (x + 4) * (x - 3) + >>> polynomial_from_roots(roots) # x^3 - 4 * x^2 - 17 * x + 60 + [1, -4, -17, 60] + """ + # Use math.prod for Python 3.8+, + prod = getattr(math, 'prod', lambda x: reduce(operator.mul, x, 1)) + roots = list(map(operator.neg, roots)) + return [ + sum(map(prod, combinations(roots, k))) for k in range(len(roots) + 1) + ] + + +def sieve(n): + """Yield the primes less than n. + + >>> list(sieve(30)) + [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] + """ + isqrt = getattr(math, 'isqrt', lambda x: int(math.sqrt(x))) + limit = isqrt(n) + 1 + data = bytearray([1]) * n + data[:2] = 0, 0 + for p in compress(range(limit), data): + data[p + p : n : p] = bytearray(len(range(p + p, n, p))) + + return compress(count(), data) + + +def batched(iterable, n): + """Batch data into lists of length *n*. The last batch may be shorter. + + >>> list(batched('ABCDEFG', 3)) + [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']] + + This recipe is from the ``itertools`` docs. This library also provides + :func:`chunked`, which has a different implementation. + """ + it = iter(iterable) + while True: + batch = list(islice(it, n)) + if not batch: + break + yield batch diff --git a/libs/common/more_itertools/recipes.pyi b/libs/common/more_itertools/recipes.pyi new file mode 100644 index 00000000..29415c5a --- /dev/null +++ b/libs/common/more_itertools/recipes.pyi @@ -0,0 +1,110 @@ +"""Stubs for more_itertools.recipes""" +from typing import ( + Any, + Callable, + Iterable, + Iterator, + List, + Optional, + Sequence, + Tuple, + TypeVar, + Union, +) +from typing_extensions import overload, Type + +# Type and type variable definitions +_T = TypeVar('_T') +_U = TypeVar('_U') + +def take(n: int, iterable: Iterable[_T]) -> List[_T]: ... +def tabulate( + function: Callable[[int], _T], start: int = ... +) -> Iterator[_T]: ... +def tail(n: int, iterable: Iterable[_T]) -> Iterator[_T]: ... +def consume(iterator: Iterable[object], n: Optional[int] = ...) -> None: ... +@overload +def nth(iterable: Iterable[_T], n: int) -> Optional[_T]: ... +@overload +def nth(iterable: Iterable[_T], n: int, default: _U) -> Union[_T, _U]: ... +def all_equal(iterable: Iterable[object]) -> bool: ... +def quantify( + iterable: Iterable[_T], pred: Callable[[_T], bool] = ... +) -> int: ... +def pad_none(iterable: Iterable[_T]) -> Iterator[Optional[_T]]: ... +def padnone(iterable: Iterable[_T]) -> Iterator[Optional[_T]]: ... +def ncycles(iterable: Iterable[_T], n: int) -> Iterator[_T]: ... +def dotproduct(vec1: Iterable[object], vec2: Iterable[object]) -> object: ... +def flatten(listOfLists: Iterable[Iterable[_T]]) -> Iterator[_T]: ... +def repeatfunc( + func: Callable[..., _U], times: Optional[int] = ..., *args: Any +) -> Iterator[_U]: ... +def pairwise(iterable: Iterable[_T]) -> Iterator[Tuple[_T, _T]]: ... +def grouper( + iterable: Iterable[_T], + n: int, + incomplete: str = ..., + fillvalue: _U = ..., +) -> Iterator[Tuple[Union[_T, _U], ...]]: ... +def roundrobin(*iterables: Iterable[_T]) -> Iterator[_T]: ... +def partition( + pred: Optional[Callable[[_T], object]], iterable: Iterable[_T] +) -> Tuple[Iterator[_T], Iterator[_T]]: ... +def powerset(iterable: Iterable[_T]) -> Iterator[Tuple[_T, ...]]: ... +def unique_everseen( + iterable: Iterable[_T], key: Optional[Callable[[_T], _U]] = ... +) -> Iterator[_T]: ... +def unique_justseen( + iterable: Iterable[_T], key: Optional[Callable[[_T], object]] = ... +) -> Iterator[_T]: ... +@overload +def iter_except( + func: Callable[[], _T], + exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], + first: None = ..., +) -> Iterator[_T]: ... +@overload +def iter_except( + func: Callable[[], _T], + exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], + first: Callable[[], _U], +) -> Iterator[Union[_T, _U]]: ... +@overload +def first_true( + iterable: Iterable[_T], *, pred: Optional[Callable[[_T], object]] = ... +) -> Optional[_T]: ... +@overload +def first_true( + iterable: Iterable[_T], + default: _U, + pred: Optional[Callable[[_T], object]] = ..., +) -> Union[_T, _U]: ... +def random_product( + *args: Iterable[_T], repeat: int = ... +) -> Tuple[_T, ...]: ... +def random_permutation( + iterable: Iterable[_T], r: Optional[int] = ... +) -> Tuple[_T, ...]: ... +def random_combination(iterable: Iterable[_T], r: int) -> Tuple[_T, ...]: ... +def random_combination_with_replacement( + iterable: Iterable[_T], r: int +) -> Tuple[_T, ...]: ... +def nth_combination( + iterable: Iterable[_T], r: int, index: int +) -> Tuple[_T, ...]: ... +def prepend(value: _T, iterator: Iterable[_U]) -> Iterator[Union[_T, _U]]: ... +def convolve(signal: Iterable[_T], kernel: Iterable[_T]) -> Iterator[_T]: ... +def before_and_after( + predicate: Callable[[_T], bool], it: Iterable[_T] +) -> Tuple[Iterator[_T], Iterator[_T]]: ... +def triplewise(iterable: Iterable[_T]) -> Iterator[Tuple[_T, _T, _T]]: ... +def sliding_window( + iterable: Iterable[_T], n: int +) -> Iterator[Tuple[_T, ...]]: ... +def subslices(iterable: Iterable[_T]) -> Iterator[List[_T]]: ... +def polynomial_from_roots(roots: Sequence[int]) -> List[int]: ... +def sieve(n: int) -> Iterator[int]: ... +def batched( + iterable: Iterable[_T], + n: int, +) -> Iterator[List[_T]]: ... diff --git a/libs/common/yaml/_yaml.cp37-win_amd64.pyd b/libs/common/yaml/_yaml.cp37-win_amd64.pyd deleted file mode 100644 index 52e813be3c82134d511fe7191f274c4ea0e00797..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 264192 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 list(_parents('b/d')) + ['b'] + >>> list(_parents('/b/d/')) + ['/b'] + >>> list(_parents('b/d/f/')) + ['b/d', 'b'] + >>> list(_parents('b')) + [] + >>> list(_parents('')) + [] + """ + return itertools.islice(_ancestry(path), 1, None) + + +def _ancestry(path): + """ + Given a path with elements separated by + posixpath.sep, generate all elements of that path + + >>> list(_ancestry('b/d')) + ['b/d', 'b'] + >>> list(_ancestry('/b/d/')) + ['/b/d', '/b'] + >>> list(_ancestry('b/d/f/')) + ['b/d/f', 'b/d', 'b'] + >>> list(_ancestry('b')) + ['b'] + >>> list(_ancestry('')) + [] + """ + path = path.rstrip(posixpath.sep) + while path and path != posixpath.sep: + yield path + path, tail = posixpath.split(path) + + +class Path: + """ + A pathlib-compatible interface for zip files. + + Consider a zip file with this structure:: + + . + ├── a.txt + └── b + ├── c.txt + └── d + └── e.txt + + >>> data = io.BytesIO() + >>> zf = zipfile.ZipFile(data, 'w') + >>> zf.writestr('a.txt', 'content of a') + >>> zf.writestr('b/c.txt', 'content of c') + >>> zf.writestr('b/d/e.txt', 'content of e') + >>> zf.filename = 'abcde.zip' + + Path accepts the zipfile object itself or a filename + + >>> root = Path(zf) + + From there, several path operations are available. + + Directory iteration (including the zip file itself): + + >>> a, b = root.iterdir() + >>> a + Path('abcde.zip', 'a.txt') + >>> b + Path('abcde.zip', 'b/') + + name property: + + >>> b.name + 'b' + + join with divide operator: + + >>> c = b / 'c.txt' + >>> c + Path('abcde.zip', 'b/c.txt') + >>> c.name + 'c.txt' + + Read text: + + >>> c.read_text() + 'content of c' + + existence: + + >>> c.exists() + True + >>> (b / 'missing.txt').exists() + False + + Coercion to string: + + >>> str(c) + 'abcde.zip/b/c.txt' + """ + + __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" + + def __init__(self, root, at=""): + self.root = ( + root + if isinstance(root, zipfile.ZipFile) + else zipfile.ZipFile(self._pathlib_compat(root)) + ) + self.at = at + + @staticmethod + def _pathlib_compat(path): + """ + For path-like objects, convert to a filename for compatibility + on Python 3.6.1 and earlier. + """ + try: + return path.__fspath__() + except AttributeError: + return str(path) + + @property + def open(self): + return functools.partial(self.root.open, self.at) + + @property + def name(self): + return posixpath.basename(self.at.rstrip("/")) + + def read_text(self, *args, **kwargs): + with self.open() as strm: + return io.TextIOWrapper(strm, *args, **kwargs).read() + + def read_bytes(self): + with self.open() as strm: + return strm.read() + + def _is_child(self, path): + return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") + + def _next(self, at): + return Path(self.root, at) + + def is_dir(self): + return not self.at or self.at.endswith("/") + + def is_file(self): + return not self.is_dir() + + def exists(self): + return self.at in self._names() + + def iterdir(self): + if not self.is_dir(): + raise ValueError("Can't listdir a file") + subs = map(self._next, self._names()) + return filter(self._is_child, subs) + + def __str__(self): + return posixpath.join(self.root.filename, self.at) + + def __repr__(self): + return self.__repr.format(self=self) + + def joinpath(self, add): + add = self._pathlib_compat(add) + next = posixpath.join(self.at, add) + next_dir = posixpath.join(self.at, add, "") + names = self._names() + return self._next(next_dir if next not in names and next_dir in names else next) + + __truediv__ = joinpath + + @staticmethod + def _implied_dirs(names): + return more_itertools.unique_everseen( + parent + "/" + for name in names + for parent in _parents(name) + if parent + "/" not in names + ) + + @classmethod + def _add_implied_dirs(cls, names): + return names + list(cls._implied_dirs(names)) + + @property + def parent(self): + parent_at = posixpath.dirname(self.at.rstrip('/')) + if parent_at: + parent_at += '/' + return self._next(parent_at) + + def _names(self): + return self._add_implied_dirs(self.root.namelist()) + + if sys.version_info < (3,): + __div__ = __truediv__ diff --git a/libs/common/zipp/__init__.py b/libs/common/zipp/__init__.py deleted file mode 100644 index ad01e27e..00000000 --- a/libs/common/zipp/__init__.py +++ /dev/null @@ -1,381 +0,0 @@ -import io -import posixpath -import zipfile -import itertools -import contextlib -import pathlib -import re -import fnmatch - -from .py310compat import text_encoding - - -__all__ = ['Path'] - - -def _parents(path): - """ - Given a path with elements separated by - posixpath.sep, generate all parents of that path. - - >>> list(_parents('b/d')) - ['b'] - >>> list(_parents('/b/d/')) - ['/b'] - >>> list(_parents('b/d/f/')) - ['b/d', 'b'] - >>> list(_parents('b')) - [] - >>> list(_parents('')) - [] - """ - return itertools.islice(_ancestry(path), 1, None) - - -def _ancestry(path): - """ - Given a path with elements separated by - posixpath.sep, generate all elements of that path - - >>> list(_ancestry('b/d')) - ['b/d', 'b'] - >>> list(_ancestry('/b/d/')) - ['/b/d', '/b'] - >>> list(_ancestry('b/d/f/')) - ['b/d/f', 'b/d', 'b'] - >>> list(_ancestry('b')) - ['b'] - >>> list(_ancestry('')) - [] - """ - path = path.rstrip(posixpath.sep) - while path and path != posixpath.sep: - yield path - path, tail = posixpath.split(path) - - -_dedupe = dict.fromkeys -"""Deduplicate an iterable in original order""" - - -def _difference(minuend, subtrahend): - """ - Return items in minuend not in subtrahend, retaining order - with O(1) lookup. - """ - return itertools.filterfalse(set(subtrahend).__contains__, minuend) - - -class InitializedState: - """ - Mix-in to save the initialization state for pickling. - """ - - def __init__(self, *args, **kwargs): - self.__args = args - self.__kwargs = kwargs - super().__init__(*args, **kwargs) - - def __getstate__(self): - return self.__args, self.__kwargs - - def __setstate__(self, state): - args, kwargs = state - super().__init__(*args, **kwargs) - - -class CompleteDirs(InitializedState, zipfile.ZipFile): - """ - A ZipFile subclass that ensures that implied directories - are always included in the namelist. - """ - - @staticmethod - def _implied_dirs(names): - parents = itertools.chain.from_iterable(map(_parents, names)) - as_dirs = (p + posixpath.sep for p in parents) - return _dedupe(_difference(as_dirs, names)) - - def namelist(self): - names = super(CompleteDirs, self).namelist() - return names + list(self._implied_dirs(names)) - - def _name_set(self): - return set(self.namelist()) - - def resolve_dir(self, name): - """ - If the name represents a directory, return that name - as a directory (with the trailing slash). - """ - names = self._name_set() - dirname = name + '/' - dir_match = name not in names and dirname in names - return dirname if dir_match else name - - @classmethod - def make(cls, source): - """ - Given a source (filename or zipfile), return an - appropriate CompleteDirs subclass. - """ - if isinstance(source, CompleteDirs): - return source - - if not isinstance(source, zipfile.ZipFile): - return cls(source) - - # Only allow for FastLookup when supplied zipfile is read-only - if 'r' not in source.mode: - cls = CompleteDirs - - source.__class__ = cls - return source - - -class FastLookup(CompleteDirs): - """ - ZipFile subclass to ensure implicit - dirs exist and are resolved rapidly. - """ - - def namelist(self): - with contextlib.suppress(AttributeError): - return self.__names - self.__names = super(FastLookup, self).namelist() - return self.__names - - def _name_set(self): - with contextlib.suppress(AttributeError): - return self.__lookup - self.__lookup = super(FastLookup, self)._name_set() - return self.__lookup - - -class Path: - """ - A pathlib-compatible interface for zip files. - - Consider a zip file with this structure:: - - . - ├── a.txt - └── b - ├── c.txt - └── d - └── e.txt - - >>> data = io.BytesIO() - >>> zf = zipfile.ZipFile(data, 'w') - >>> zf.writestr('a.txt', 'content of a') - >>> zf.writestr('b/c.txt', 'content of c') - >>> zf.writestr('b/d/e.txt', 'content of e') - >>> zf.filename = 'mem/abcde.zip' - - Path accepts the zipfile object itself or a filename - - >>> root = Path(zf) - - From there, several path operations are available. - - Directory iteration (including the zip file itself): - - >>> a, b = root.iterdir() - >>> a - Path('mem/abcde.zip', 'a.txt') - >>> b - Path('mem/abcde.zip', 'b/') - - name property: - - >>> b.name - 'b' - - join with divide operator: - - >>> c = b / 'c.txt' - >>> c - Path('mem/abcde.zip', 'b/c.txt') - >>> c.name - 'c.txt' - - Read text: - - >>> c.read_text() - 'content of c' - - existence: - - >>> c.exists() - True - >>> (b / 'missing.txt').exists() - False - - Coercion to string: - - >>> import os - >>> str(c).replace(os.sep, posixpath.sep) - 'mem/abcde.zip/b/c.txt' - - At the root, ``name``, ``filename``, and ``parent`` - resolve to the zipfile. Note these attributes are not - valid and will raise a ``ValueError`` if the zipfile - has no filename. - - >>> root.name - 'abcde.zip' - >>> str(root.filename).replace(os.sep, posixpath.sep) - 'mem/abcde.zip' - >>> str(root.parent) - 'mem' - """ - - __repr = "{self.__class__.__name__}({self.root.filename!r}, {self.at!r})" - - def __init__(self, root, at=""): - """ - Construct a Path from a ZipFile or filename. - - Note: When the source is an existing ZipFile object, - its type (__class__) will be mutated to a - specialized type. If the caller wishes to retain the - original type, the caller should either create a - separate ZipFile object or pass a filename. - """ - self.root = FastLookup.make(root) - self.at = at - - def __eq__(self, other): - """ - >>> Path(zipfile.ZipFile(io.BytesIO(), 'w')) == 'foo' - False - """ - if self.__class__ is not other.__class__: - return NotImplemented - return (self.root, self.at) == (other.root, other.at) - - def __hash__(self): - return hash((self.root, self.at)) - - def open(self, mode='r', *args, pwd=None, **kwargs): - """ - Open this entry as text or binary following the semantics - of ``pathlib.Path.open()`` by passing arguments through - to io.TextIOWrapper(). - """ - if self.is_dir(): - raise IsADirectoryError(self) - zip_mode = mode[0] - if not self.exists() and zip_mode == 'r': - raise FileNotFoundError(self) - stream = self.root.open(self.at, zip_mode, pwd=pwd) - if 'b' in mode: - if args or kwargs: - raise ValueError("encoding args invalid for binary operation") - return stream - else: - kwargs["encoding"] = text_encoding(kwargs.get("encoding")) - return io.TextIOWrapper(stream, *args, **kwargs) - - @property - def name(self): - return pathlib.Path(self.at).name or self.filename.name - - @property - def suffix(self): - return pathlib.Path(self.at).suffix or self.filename.suffix - - @property - def suffixes(self): - return pathlib.Path(self.at).suffixes or self.filename.suffixes - - @property - def stem(self): - return pathlib.Path(self.at).stem or self.filename.stem - - @property - def filename(self): - return pathlib.Path(self.root.filename).joinpath(self.at) - - def read_text(self, *args, **kwargs): - kwargs["encoding"] = text_encoding(kwargs.get("encoding")) - with self.open('r', *args, **kwargs) as strm: - return strm.read() - - def read_bytes(self): - with self.open('rb') as strm: - return strm.read() - - def _is_child(self, path): - return posixpath.dirname(path.at.rstrip("/")) == self.at.rstrip("/") - - def _next(self, at): - return self.__class__(self.root, at) - - def is_dir(self): - return not self.at or self.at.endswith("/") - - def is_file(self): - return self.exists() and not self.is_dir() - - def exists(self): - return self.at in self.root._name_set() - - def iterdir(self): - if not self.is_dir(): - raise ValueError("Can't listdir a file") - subs = map(self._next, self.root.namelist()) - return filter(self._is_child, subs) - - def match(self, path_pattern): - return pathlib.Path(self.at).match(path_pattern) - - def is_symlink(self): - """ - Return whether this path is a symlink. Always false (python/cpython#82102). - """ - return False - - def _descendants(self): - for child in self.iterdir(): - yield child - if child.is_dir(): - yield from child._descendants() - - def glob(self, pattern): - if not pattern: - raise ValueError("Unacceptable pattern: {!r}".format(pattern)) - - matches = re.compile(fnmatch.translate(pattern)).fullmatch - return ( - child - for child in self._descendants() - if matches(str(child.relative_to(self))) - ) - - def rglob(self, pattern): - return self.glob(f'**/{pattern}') - - def relative_to(self, other, *extra): - return posixpath.relpath(str(self), str(other.joinpath(*extra))) - - def __str__(self): - return posixpath.join(self.root.filename, self.at) - - def __repr__(self): - return self.__repr.format(self=self) - - def joinpath(self, *other): - next = posixpath.join(self.at, *other) - return self._next(self.root.resolve_dir(next)) - - __truediv__ = joinpath - - @property - def parent(self): - if not self.at: - return self.filename.parent - parent_at = posixpath.dirname(self.at.rstrip('/')) - if parent_at: - parent_at += '/' - return self._next(parent_at) diff --git a/libs/common/zipp/py310compat.py b/libs/common/zipp/py310compat.py deleted file mode 100644 index 8244124c..00000000 --- a/libs/common/zipp/py310compat.py +++ /dev/null @@ -1,12 +0,0 @@ -import sys -import io - - -te_impl = 'lambda encoding, stacklevel=2, /: encoding' -te_impl_37 = te_impl.replace(', /', '') -_text_encoding = eval(te_impl) if sys.version_info > (3, 8) else eval(te_impl_37) - - -text_encoding = ( - io.text_encoding if sys.version_info > (3, 10) else _text_encoding # type: ignore -)