Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-more-itertools for
openSUSE:Factory checked in at 2021-03-10 08:49:38
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-more-itertools (Old)
and /work/SRC/openSUSE:Factory/.python-more-itertools.new.2378 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-more-itertools"
Wed Mar 10 08:49:38 2021 rev:14 rq:876826 version:8.7.0
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-more-itertools/python-more-itertools.changes
2020-12-24 19:41:28.467220043 +0100
+++
/work/SRC/openSUSE:Factory/.python-more-itertools.new.2378/python-more-itertools.changes
2021-03-10 08:49:50.350429692 +0100
@@ -1,0 +2,19 @@
+Thu Mar 4 21:03:34 UTC 2021 - Dirk M??ller <[email protected]>
+
+- update to 8.7.0:
+ * New functions
+ * :func:`convolve`
+ * :func:`product_index`, :func:`combination_index`, and
:func:`permutation_index`
+ * :func:`value_chain`
+ * Changes to existing functions
+ * :func:`distinct_combinations` now uses a non-recursive algorithm
+ * :func:`pad_none` is now the preferred name for :func:`padnone`, though
the latter remains available.
+ * :func:`pairwise` will now use the Python standard library implementation
on Python 3.10+
+ * :func:`sort_together` now accepts a ``key`` argument
+ * :func:`seekable` now has a ``peek`` method, and can indicate whether the
iterator it's wrapping is exhausted
+ * :func:`time_limited` can now indicate whether its iterator has expired
+ * The implementation of :func:`unique_everseen` was improved
+ * Other changes:
+ * Various documentation updates
+
+-------------------------------------------------------------------
Old:
----
more-itertools-8.6.0.tar.gz
New:
----
more-itertools-8.7.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-more-itertools.spec ++++++
--- /var/tmp/diff_new_pack.hwtvNN/_old 2021-03-10 08:49:50.902430262 +0100
+++ /var/tmp/diff_new_pack.hwtvNN/_new 2021-03-10 08:49:50.902430262 +0100
@@ -1,7 +1,7 @@
#
# spec file for package python-more-itertools
#
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-more-itertools
-Version: 8.6.0
+Version: 8.7.0
Release: 0
Summary: More routines for operating on iterables, beyond itertools
License: MIT
++++++ more-itertools-8.6.0.tar.gz -> more-itertools-8.7.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/PKG-INFO
new/more-itertools-8.7.0/PKG-INFO
--- old/more-itertools-8.6.0/PKG-INFO 2020-10-30 14:50:39.324085500 +0100
+++ new/more-itertools-8.7.0/PKG-INFO 2021-02-08 02:52:59.821831700 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: more-itertools
-Version: 8.6.0
+Version: 8.7.0
Summary: More routines for operating on iterables, beyond itertools
Home-page: https://github.com/more-itertools/more-itertools
Author: Erik Rose
@@ -62,9 +62,11 @@
| | `zip_offset
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_offset>`_,
|
| | `zip_equal
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_equal>`_,
|
| | `dotproduct
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.dotproduct>`_,
|
+ | | `convolve
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.convolve>`_,
|
| | `flatten
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.flatten>`_,
|
| | `roundrobin
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.roundrobin>`_,
|
- | | `prepend
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.prepend>`_
|
+ | | `prepend
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.prepend>`_,
|
+ | | `value_chain
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.value_chain>`_
|
+------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Summarizing | `ilen
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.ilen>`_,
|
| | `unique_to_each
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_to_each>`_,
|
@@ -101,6 +103,9 @@
| | `circular_shifts
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.circular_shifts>`_,
|
| | `partitions
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.partitions>`_,
|
| | `set_partitions
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.set_partitions>`_,
|
+ | | `product_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.product_index>`_,
|
+ | | `combination_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.combination_index>`_,
|
+ | | `permutation_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.permutation_index>`_,
|
| | `powerset
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset>`_,
|
| | `random_product
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_product>`_,
|
| | `random_permutation
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_permutation>`_,
|
@@ -179,7 +184,7 @@
Blog posts about ``more-itertools``:
* `Yo, I heard you like decorators
<https://www.bbayles.com/index/decorator_factory>`__
- * `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__
+ * `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__
(`Alternate <https://dev.to/martinheinz/tour-of-python-itertools-4122>`__)
Development
@@ -197,6 +202,26 @@
:noindex:
+ 8.7.0
+ -----
+
+ * New functions
+ * convolve (from the Python itertools docs)
+ * product_index, combination_index, and permutation_index (thanks
to N8Brooks)
+ * value_chain (thanks to jenstroeger)
+
+ * Changes to existing functions
+ * distinct_combinations now uses a non-recursive algorithm (thanks
to knutdrand)
+ * pad_none is now the preferred name for padnone, though the
latter remains available.
+ * pairwise will now use the Python standard library implementation
on Python 3.10+
+ * sort_together now accepts a ``key`` argument (thanks to
brianmaissy)
+ * seekable now has a ``peek`` method, and can indicate whether the
iterator it's wrapping is exhausted (thanks to gsakkis)
+ * time_limited can now indicate whether its iterator has expired
(thanks to roysmith)
+ * The implementation of unique_everseen was improved (thanks to
plammens)
+
+ * Other changes:
+ * Various documentation updates (thanks to cthoyt, Evantm, and
cyphase)
+
8.6.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/README.rst
new/more-itertools-8.7.0/README.rst
--- old/more-itertools-8.6.0/README.rst 2020-10-30 14:50:21.000000000 +0100
+++ new/more-itertools-8.7.0/README.rst 2021-02-06 14:41:48.000000000 +0100
@@ -54,9 +54,11 @@
| | `zip_offset
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_offset>`_,
|
| | `zip_equal
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_equal>`_,
|
| | `dotproduct
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.dotproduct>`_,
|
+| | `convolve
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.convolve>`_,
|
| | `flatten
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.flatten>`_,
|
| | `roundrobin
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.roundrobin>`_,
|
-| | `prepend
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.prepend>`_
|
+| | `prepend
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.prepend>`_,
|
+| | `value_chain
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.value_chain>`_
|
+------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Summarizing | `ilen
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.ilen>`_,
|
| | `unique_to_each
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_to_each>`_,
|
@@ -93,6 +95,9 @@
| | `circular_shifts
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.circular_shifts>`_,
|
| | `partitions
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.partitions>`_,
|
| | `set_partitions
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.set_partitions>`_,
|
+| | `product_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.product_index>`_,
|
+| | `combination_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.combination_index>`_,
|
+| | `permutation_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.permutation_index>`_,
|
| | `powerset
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset>`_,
|
| | `random_product
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_product>`_,
|
| | `random_permutation
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_permutation>`_,
|
@@ -171,7 +176,7 @@
Blog posts about ``more-itertools``:
* `Yo, I heard you like decorators
<https://www.bbayles.com/index/decorator_factory>`__
-* `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__
+* `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__ (`Alternate
<https://dev.to/martinheinz/tour-of-python-itertools-4122>`__)
Development
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/docs/api.rst
new/more-itertools-8.7.0/docs/api.rst
--- old/more-itertools-8.6.0/docs/api.rst 2020-10-30 14:50:21.000000000
+0100
+++ new/more-itertools-8.7.0/docs/api.rst 2021-02-06 14:41:48.000000000
+0100
@@ -92,10 +92,11 @@
**Itertools recipes**
-.. autofunction:: padnone
+.. function:: padnone
+ :noindex:
+.. autofunction:: pad_none
.. autofunction:: ncycles
-
Combining
=========
@@ -106,9 +107,10 @@
**New itertools**
.. autofunction:: collapse
-.. autofunction:: sort_together
.. autofunction:: interleave
.. autofunction:: interleave_longest
+.. autofunction:: sort_together
+.. autofunction:: value_chain
.. autofunction:: zip_offset(*iterables, offsets, longest=False,
fillvalue=None)
.. autofunction:: zip_equal
@@ -117,6 +119,7 @@
**Itertools recipes**
.. autofunction:: dotproduct
+.. autofunction:: convolve
.. autofunction:: flatten
.. autofunction:: roundrobin
.. autofunction:: prepend
@@ -199,6 +202,9 @@
.. autofunction:: circular_shifts
.. autofunction:: partitions
.. autofunction:: set_partitions
+.. autofunction:: product_index
+.. autofunction:: combination_index
+.. autofunction:: permutation_index
----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/docs/versions.rst
new/more-itertools-8.7.0/docs/versions.rst
--- old/more-itertools-8.6.0/docs/versions.rst 2020-10-30 14:50:21.000000000
+0100
+++ new/more-itertools-8.7.0/docs/versions.rst 2021-02-08 02:52:56.000000000
+0100
@@ -5,6 +5,26 @@
.. automodule:: more_itertools
:noindex:
+8.7.0
+-----
+
+* New functions
+ * :func:`convolve` (from the Python itertools docs)
+ * :func:`product_index`, :func:`combination_index`, and
:func:`permutation_index` (thanks to N8Brooks)
+ * :func:`value_chain` (thanks to jenstroeger)
+
+* Changes to existing functions
+ * :func:`distinct_combinations` now uses a non-recursive algorithm (thanks
to knutdrand)
+ * :func:`pad_none` is now the preferred name for :func:`padnone`, though
the latter remains available.
+ * :func:`pairwise` will now use the Python standard library implementation
on Python 3.10+
+ * :func:`sort_together` now accepts a ``key`` argument (thanks to
brianmaissy)
+ * :func:`seekable` now has a ``peek`` method, and can indicate whether the
iterator it's wrapping is exhausted (thanks to gsakkis)
+ * :func:`time_limited` can now indicate whether its iterator has expired
(thanks to roysmith)
+ * The implementation of :func:`unique_everseen` was improved (thanks to
plammens)
+
+* Other changes:
+ * Various documentation updates (thanks to cthoyt, Evantm, and cyphase)
+
8.6.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/more_itertools/__init__.py
new/more-itertools-8.7.0/more_itertools/__init__.py
--- old/more-itertools-8.6.0/more_itertools/__init__.py 2020-10-30
14:50:21.000000000 +0100
+++ new/more-itertools-8.7.0/more_itertools/__init__.py 2021-02-08
02:52:56.000000000 +0100
@@ -1,4 +1,4 @@
from .more import * # noqa
from .recipes import * # noqa
-__version__ = '8.6.0'
+__version__ = '8.7.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/more_itertools/more.py
new/more-itertools-8.7.0/more_itertools/more.py
--- old/more-itertools-8.6.0/more_itertools/more.py 2020-10-30
14:50:21.000000000 +0100
+++ new/more-itertools-8.7.0/more_itertools/more.py 2021-02-08
02:52:56.000000000 +0100
@@ -114,6 +114,10 @@
'zip_offset',
'windowed_complete',
'all_unique',
+ 'value_chain',
+ 'product_index',
+ 'combination_index',
+ 'permutation_index',
]
_marker = object()
@@ -134,7 +138,7 @@
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 then ``ValueError`` will be raised before the last
+ ``True``, then ``ValueError`` will be raised before the last
list is yielded.
"""
@@ -1134,7 +1138,7 @@
[(1, 2, 3), (4, 5, 6), (7, 8)]
If the length of *seq* is not divisible by *n* and *strict* is
- ``True``, then then ``ValueError`` will be raised before the last
+ ``True``, then ``ValueError`` will be raised before the last
slice is yielded.
This function will only work for iterables that support slicing.
@@ -1568,7 +1572,7 @@
return zip(*staggered)
-def sort_together(iterables, key_list=(0,), reverse=False):
+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.
@@ -1590,18 +1594,47 @@
>>> 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')]
"""
- return list(
- zip(
- *sorted(
- zip(*iterables), key=itemgetter(*key_list), reverse=reverse
+ 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))
)
@@ -2463,8 +2496,8 @@
set to something other than ``None``, it will skip the first element when
computing successive differences.
- >>> iterable = [100, 101, 103, 106] # accumate([1, 2, 3], initial=100)
- >>> list(difference(iterable, initial=100))
+ >>> iterable = [10, 11, 13, 16] # accumulate([1, 2, 3], initial=10)
+ >>> list(difference(iterable, initial=10))
[1, 2, 3]
"""
@@ -2558,6 +2591,27 @@
>>> 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:
@@ -2615,6 +2669,25 @@
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)
@@ -2994,9 +3067,11 @@
yield from set_partitions_helper(L, k)
-def time_limited(limit_seconds, iterable):
+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():
@@ -3004,9 +3079,11 @@
... yield 2
... sleep(0.2)
... yield 3
- >>> iterable = generator()
- >>> list(time_limited(0.1, iterable))
+ >>> 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
@@ -3014,14 +3091,25 @@
the iterable, the function will run for 2 seconds and not yield anything.
"""
- if limit_seconds < 0:
- raise ValueError('limit_seconds must be positive')
- start_time = monotonic()
- for item in iterable:
- if monotonic() - start_time > limit_seconds:
- break
- yield item
+ 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):
@@ -3117,11 +3205,29 @@
raise ValueError('r must be non-negative')
elif r == 0:
yield ()
- else:
- pool = tuple(iterable)
- for i, prefix in unique_everseen(enumerate(pool), key=itemgetter(1)):
- for suffix in distinct_combinations(pool[i + 1 :], r - 1):
- yield (prefix,) + suffix
+ 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):
@@ -3494,6 +3600,11 @@
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))
@@ -3522,6 +3633,12 @@
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)
@@ -3552,3 +3669,123 @@
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 versiosn 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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/more_itertools/recipes.py
new/more-itertools-8.7.0/more_itertools/recipes.py
--- old/more-itertools-8.6.0/more_itertools/recipes.py 2020-10-27
21:53:24.000000000 +0100
+++ new/more-itertools-8.7.0/more_itertools/recipes.py 2021-02-06
14:41:54.000000000 +0100
@@ -27,6 +27,7 @@
__all__ = [
'all_equal',
'consume',
+ 'convolve',
'dotproduct',
'first_true',
'flatten',
@@ -36,6 +37,7 @@
'nth',
'nth_combination',
'padnone',
+ 'pad_none',
'pairwise',
'partition',
'powerset',
@@ -177,10 +179,10 @@
return sum(map(pred, iterable))
-def padnone(iterable):
+def pad_none(iterable):
"""Returns the sequence of elements and then returns ``None`` indefinitely.
- >>> take(5, padnone(range(3)))
+ >>> take(5, pad_none(range(3)))
[0, 1, 2, None, None]
Useful for emulating the behavior of the built-in :func:`map` function.
@@ -191,6 +193,9 @@
return chain(iterable, repeat(None))
+padnone = pad_none
+
+
def ncycles(iterable, n):
"""Returns the sequence elements *n* times
@@ -250,16 +255,30 @@
return starmap(func, repeat(args, times))
-def pairwise(iterable):
+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)
- return zip(a, b)
+ 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__
def grouper(iterable, n, fillvalue=None):
@@ -382,12 +401,13 @@
``key=lambda x: frozenset(x.items())`` can be used.
"""
+ key = key if key is not None else lambda x: x
seenset = set()
seenset_add = seenset.add
seenlist = []
seenlist_add = seenlist.append
- iterable, keys = tee(iterable)
- for element, k in zip(iterable, map(key, keys) if key else keys):
+ for element in iterable:
+ k = key(element)
try:
if k not in seenset:
seenset_add(k)
@@ -530,6 +550,12 @@
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)
@@ -566,7 +592,28 @@
>>> list(prepend(value, iterator))
['0', '1', '2', '3']
- To prepend multiple values, see :func:`itertools.chain`.
+ 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))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/more-itertools-8.6.0/more_itertools.egg-info/PKG-INFO
new/more-itertools-8.7.0/more_itertools.egg-info/PKG-INFO
--- old/more-itertools-8.6.0/more_itertools.egg-info/PKG-INFO 2020-10-30
14:50:39.000000000 +0100
+++ new/more-itertools-8.7.0/more_itertools.egg-info/PKG-INFO 2021-02-08
02:52:59.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: more-itertools
-Version: 8.6.0
+Version: 8.7.0
Summary: More routines for operating on iterables, beyond itertools
Home-page: https://github.com/more-itertools/more-itertools
Author: Erik Rose
@@ -62,9 +62,11 @@
| | `zip_offset
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_offset>`_,
|
| | `zip_equal
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.zip_equal>`_,
|
| | `dotproduct
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.dotproduct>`_,
|
+ | | `convolve
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.convolve>`_,
|
| | `flatten
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.flatten>`_,
|
| | `roundrobin
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.roundrobin>`_,
|
- | | `prepend
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.prepend>`_
|
+ | | `prepend
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.prepend>`_,
|
+ | | `value_chain
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.value_chain>`_
|
+------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Summarizing | `ilen
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.ilen>`_,
|
| | `unique_to_each
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.unique_to_each>`_,
|
@@ -101,6 +103,9 @@
| | `circular_shifts
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.circular_shifts>`_,
|
| | `partitions
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.partitions>`_,
|
| | `set_partitions
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.set_partitions>`_,
|
+ | | `product_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.product_index>`_,
|
+ | | `combination_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.combination_index>`_,
|
+ | | `permutation_index
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.permutation_index>`_,
|
| | `powerset
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.powerset>`_,
|
| | `random_product
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_product>`_,
|
| | `random_permutation
<https://more-itertools.readthedocs.io/en/stable/api.html#more_itertools.random_permutation>`_,
|
@@ -179,7 +184,7 @@
Blog posts about ``more-itertools``:
* `Yo, I heard you like decorators
<https://www.bbayles.com/index/decorator_factory>`__
- * `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__
+ * `Tour of Python Itertools <https://martinheinz.dev/blog/16>`__
(`Alternate <https://dev.to/martinheinz/tour-of-python-itertools-4122>`__)
Development
@@ -197,6 +202,26 @@
:noindex:
+ 8.7.0
+ -----
+
+ * New functions
+ * convolve (from the Python itertools docs)
+ * product_index, combination_index, and permutation_index (thanks
to N8Brooks)
+ * value_chain (thanks to jenstroeger)
+
+ * Changes to existing functions
+ * distinct_combinations now uses a non-recursive algorithm (thanks
to knutdrand)
+ * pad_none is now the preferred name for padnone, though the
latter remains available.
+ * pairwise will now use the Python standard library implementation
on Python 3.10+
+ * sort_together now accepts a ``key`` argument (thanks to
brianmaissy)
+ * seekable now has a ``peek`` method, and can indicate whether the
iterator it's wrapping is exhausted (thanks to gsakkis)
+ * time_limited can now indicate whether its iterator has expired
(thanks to roysmith)
+ * The implementation of unique_everseen was improved (thanks to
plammens)
+
+ * Other changes:
+ * Various documentation updates (thanks to cthoyt, Evantm, and
cyphase)
+
8.6.0
-----
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/setup.cfg
new/more-itertools-8.7.0/setup.cfg
--- old/more-itertools-8.6.0/setup.cfg 2020-10-30 14:50:39.324085500 +0100
+++ new/more-itertools-8.7.0/setup.cfg 2021-02-08 02:52:59.821831700 +0100
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 8.6.0
+current_version = 8.7.0
commit = True
tag = False
files = more_itertools/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/tests/test_more.py
new/more-itertools-8.7.0/tests/test_more.py
--- old/more-itertools-8.6.0/tests/test_more.py 2020-10-16 23:23:26.000000000
+0200
+++ new/more-itertools-8.7.0/tests/test_more.py 2021-02-06 14:41:48.000000000
+0100
@@ -223,15 +223,22 @@
self.assertRaises(ValueError, lambda: mi.nth_or_last(range(0), 0))
-class PeekableTests(TestCase):
- """Tests for ``peekable()`` behavor not incidentally covered by testing
- ``collate()``
+class PeekableMixinTests:
+ """Common tests for ``peekable()`` and ``seekable()`` behavior"""
- """
+ cls = None
+
+ def test_passthrough(self):
+ """Iterating a peekable without using ``peek()`` or ``prepend()``
+ should just give the underlying iterable's elements (a trivial test but
+ useful to set a baseline in case something goes wrong)"""
+ expected = [1, 2, 3, 4, 5]
+ actual = list(self.cls(expected))
+ self.assertEqual(actual, expected)
def test_peek_default(self):
"""Make sure passing a default into ``peek()`` works."""
- p = mi.peekable([])
+ p = self.cls([])
self.assertEqual(p.peek(7), 7)
def test_truthiness(self):
@@ -239,10 +246,10 @@
the iterable.
"""
- p = mi.peekable([])
+ p = self.cls([])
self.assertFalse(p)
- p = mi.peekable(range(3))
+ p = self.cls(range(3))
self.assertTrue(p)
def test_simple_peeking(self):
@@ -250,11 +257,21 @@
iterator, respectively.
"""
- p = mi.peekable(range(10))
+ p = self.cls(range(10))
self.assertEqual(next(p), 0)
self.assertEqual(p.peek(), 1)
+ self.assertEqual(p.peek(), 1)
self.assertEqual(next(p), 1)
+
+class PeekableTests(PeekableMixinTests, TestCase):
+ """Tests for ``peekable()`` behavior not incidentally covered by testing
+ ``collate()``
+
+ """
+
+ cls = mi.peekable
+
def test_indexing(self):
"""
Indexing into the peekable shouldn't advance the iterator.
@@ -334,14 +351,6 @@
self.assertEqual(old_cache, list(p._cache))
self.assertEqual(list(p), list(iterable))
- def test_passthrough(self):
- """Iterating a peekable without using ``peek()`` or ``prepend()``
- should just give the underlying iterable's elements (a trivial test but
- useful to set a baseline in case something goes wrong)"""
- expected = [1, 2, 3, 4, 5]
- actual = list(mi.peekable(expected))
- self.assertEqual(actual, expected)
-
# prepend() behavior tests
def test_prepend(self):
@@ -1920,6 +1929,44 @@
IndexError, lambda: mi.sort_together(iterables, key_list=(5,))
)
+ def test_key_function(self):
+ """tests `key` function, including interaction with `key_list`"""
+ iterables = [
+ ['GA', 'GA', 'GA', 'CT', 'CT', 'CT'],
+ ['May', 'Aug.', 'May', 'June', 'July', 'July'],
+ [97, 20, 100, 70, 100, 20],
+ ]
+ self.assertEqual(
+ mi.sort_together(iterables, key=lambda x: x),
+ [
+ ('CT', 'CT', 'CT', 'GA', 'GA', 'GA'),
+ ('June', 'July', 'July', 'May', 'Aug.', 'May'),
+ (70, 100, 20, 97, 20, 100),
+ ],
+ )
+ self.assertEqual(
+ mi.sort_together(iterables, key=lambda x: x[::-1]),
+ [
+ ('GA', 'GA', 'GA', 'CT', 'CT', 'CT'),
+ ('May', 'Aug.', 'May', 'June', 'July', 'July'),
+ (97, 20, 100, 70, 100, 20),
+ ],
+ )
+ self.assertEqual(
+ mi.sort_together(
+ iterables,
+ key_list=(0, 2),
+ key=lambda state, number: number
+ if state == 'CT'
+ else 2 * number,
+ ),
+ [
+ ('CT', 'GA', 'CT', 'CT', 'GA', 'GA'),
+ ('July', 'Aug.', 'June', 'July', 'May', 'May'),
+ (20, 20, 70, 100, 97, 100),
+ ],
+ )
+
def test_reverse(self):
"""tests `reverse` to ensure a reverse sort for `key_list` iterables"""
iterables = [
@@ -3045,7 +3092,9 @@
self.assertEqual(actual, original)
-class SeekableTest(TestCase):
+class SeekableTest(PeekableMixinTests, TestCase):
+ cls = mi.seekable
+
def test_exhaustion_reset(self):
iterable = [str(n) for n in range(10)]
@@ -3663,16 +3712,25 @@
sleep(0.2)
yield 3
- iterable = generator()
- actual = list(mi.time_limited(0.1, iterable))
+ iterable = mi.time_limited(0.1, generator())
+ actual = list(iterable)
expected = [1, 2]
self.assertEqual(actual, expected)
+ self.assertTrue(iterable.timed_out)
+
+ def test_complete(self):
+ iterable = mi.time_limited(2, iter(range(10)))
+ actual = list(iterable)
+ expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ self.assertEqual(actual, expected)
+ self.assertFalse(iterable.timed_out)
def test_zero_limit(self):
- iterable = count()
- actual = list(mi.time_limited(0, iterable))
+ iterable = mi.time_limited(0, count())
+ actual = list(iterable)
expected = []
self.assertEqual(actual, expected)
+ self.assertTrue(iterable.timed_out)
def test_invalid_limit(self):
with self.assertRaises(ValueError):
@@ -4134,3 +4192,174 @@
def test_invalid_index(self):
with self.assertRaises(IndexError):
mi.nth_product(24, 'ab', 'cde', 'fghi')
+
+
+class ValueChainTests(TestCase):
+ def test_empty(self):
+ actual = list(mi.value_chain())
+ expected = []
+ self.assertEqual(actual, expected)
+
+ def test_simple(self):
+ actual = list(mi.value_chain(1, 2.71828, False, 'foo'))
+ expected = [1, 2.71828, False, 'foo']
+ self.assertEqual(actual, expected)
+
+ def test_more(self):
+ actual = list(mi.value_chain(b'bar', [1, 2, 3], 4, {'key': 1}))
+ expected = [b'bar', 1, 2, 3, 4, 'key']
+ self.assertEqual(actual, expected)
+
+ def test_empty_lists(self):
+ actual = list(mi.value_chain(1, 2, [], [3, 4]))
+ expected = [1, 2, 3, 4]
+ self.assertEqual(actual, expected)
+
+ def test_complex(self):
+ obj = object()
+ actual = list(
+ mi.value_chain(
+ (1, (2, (3,))),
+ ['foo', ['bar', ['baz']], 'tic'],
+ {'key': {'foo': 1}},
+ obj,
+ )
+ )
+ expected = [1, (2, (3,)), 'foo', ['bar', ['baz']], 'tic', 'key', obj]
+ self.assertEqual(actual, expected)
+
+
+class ProductIndexTests(TestCase):
+ def test_basic(self):
+ iterables = ['ab', 'cdef', 'ghi']
+ first_index = {}
+ for index, element in enumerate(product(*iterables)):
+ actual = mi.product_index(element, *iterables)
+ expected = first_index.setdefault(element, index)
+ self.assertEqual(actual, expected)
+
+ def test_multiplicity(self):
+ iterables = ['ab', 'bab', 'cab']
+ first_index = {}
+ for index, element in enumerate(product(*iterables)):
+ actual = mi.product_index(element, *iterables)
+ expected = first_index.setdefault(element, index)
+ self.assertEqual(actual, expected)
+
+ def test_long(self):
+ actual = mi.product_index((1, 3, 12), range(101), range(22), range(53))
+ expected = 1337
+ self.assertEqual(actual, expected)
+
+ def test_invalid_empty(self):
+ with self.assertRaises(ValueError):
+ mi.product_index('', 'ab', 'cde', 'fghi')
+
+ def test_invalid_small(self):
+ with self.assertRaises(ValueError):
+ mi.product_index('ac', 'ab', 'cde', 'fghi')
+
+ def test_invalid_large(self):
+ with self.assertRaises(ValueError):
+ mi.product_index('achi', 'ab', 'cde', 'fghi')
+
+ def test_invalid_match(self):
+ with self.assertRaises(ValueError):
+ mi.product_index('axf', 'ab', 'cde', 'fghi')
+
+
+class CombinationIndexTests(TestCase):
+ def test_r_less_than_n(self):
+ iterable = 'abcdefg'
+ r = 4
+ first_index = {}
+ for index, element in enumerate(combinations(iterable, r)):
+ actual = mi.combination_index(element, iterable)
+ expected = first_index.setdefault(element, index)
+ self.assertEqual(actual, expected)
+
+ def test_r_equal_to_n(self):
+ iterable = 'abcd'
+ r = len(iterable)
+ first_index = {}
+ for index, element in enumerate(combinations(iterable, r=r)):
+ actual = mi.combination_index(element, iterable)
+ expected = first_index.setdefault(element, index)
+ self.assertEqual(actual, expected)
+
+ def test_multiplicity(self):
+ iterable = 'abacba'
+ r = 3
+ first_index = {}
+ for index, element in enumerate(combinations(iterable, r)):
+ actual = mi.combination_index(element, iterable)
+ expected = first_index.setdefault(element, index)
+ self.assertEqual(actual, expected)
+
+ def test_null(self):
+ actual = mi.combination_index(tuple(), [])
+ expected = 0
+ self.assertEqual(actual, expected)
+
+ def test_long(self):
+ actual = mi.combination_index((2, 12, 35, 126), range(180))
+ expected = 2000000
+ self.assertEqual(actual, expected)
+
+ def test_invalid_order(self):
+ with self.assertRaises(ValueError):
+ mi.combination_index(tuple('acb'), 'abcde')
+
+ def test_invalid_large(self):
+ with self.assertRaises(ValueError):
+ mi.combination_index(tuple('abcdefg'), 'abcdef')
+
+ def test_invalid_match(self):
+ with self.assertRaises(ValueError):
+ mi.combination_index(tuple('axe'), 'abcde')
+
+
+class PermutationIndexTests(TestCase):
+ def test_r_less_than_n(self):
+ iterable = 'abcdefg'
+ r = 4
+ first_index = {}
+ for index, element in enumerate(permutations(iterable, r)):
+ actual = mi.permutation_index(element, iterable)
+ expected = first_index.setdefault(element, index)
+ self.assertEqual(actual, expected)
+
+ def test_r_equal_to_n(self):
+ iterable = 'abcd'
+ first_index = {}
+ for index, element in enumerate(permutations(iterable)):
+ actual = mi.permutation_index(element, iterable)
+ expected = first_index.setdefault(element, index)
+ self.assertEqual(actual, expected)
+
+ def test_multiplicity(self):
+ iterable = 'abacba'
+ r = 3
+ first_index = {}
+ for index, element in enumerate(permutations(iterable, r)):
+ actual = mi.permutation_index(element, iterable)
+ expected = first_index.setdefault(element, index)
+ self.assertEqual(actual, expected)
+
+ def test_null(self):
+ actual = mi.permutation_index(tuple(), [])
+ expected = 0
+ self.assertEqual(actual, expected)
+
+ def test_long(self):
+ actual = mi.permutation_index((2, 12, 35, 126), range(180))
+ expected = 11631678
+ self.assertEqual(actual, expected)
+
+ def test_invalid_large(self):
+ with self.assertRaises(ValueError):
+ mi.permutation_index(tuple('abcdefg'), 'abcdef')
+
+ def test_invalid_match(self):
+ with self.assertRaises(ValueError):
+ mi.permutation_index(tuple('axe'), 'abcde')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/more-itertools-8.6.0/tests/test_recipes.py
new/more-itertools-8.7.0/tests/test_recipes.py
--- old/more-itertools-8.6.0/tests/test_recipes.py 2020-10-27
21:50:38.000000000 +0100
+++ new/more-itertools-8.7.0/tests/test_recipes.py 2021-01-10
03:27:24.000000000 +0100
@@ -1,7 +1,7 @@
import warnings
from doctest import DocTestSuite
-from itertools import combinations, permutations
+from itertools import combinations, count, permutations
from math import factorial
from unittest import TestCase
@@ -166,13 +166,14 @@
class PadnoneTests(TestCase):
- """Tests for ``padnone()``"""
-
- def test_happy_path(self):
- """wrapper iterator should return None indefinitely"""
- r = range(2)
- p = mi.padnone(r)
- self.assertEqual([0, 1, None, None], [next(p) for _ in range(4)])
+ def test_basic(self):
+ iterable = range(2)
+ for func in (mi.pad_none, mi.padnone):
+ with self.subTest(func=func):
+ p = func(iterable)
+ self.assertEqual(
+ [0, 1, None, None], [next(p) for _ in range(4)]
+ )
class NcyclesTests(TestCase):
@@ -654,3 +655,33 @@
actual = tuple(mi.prepend(value, iterator))
expected = ('ab',) + tuple('cdefg')
self.assertEqual(actual, expected)
+
+
+class Convolvetests(TestCase):
+ def test_moving_average(self):
+ signal = iter([10, 20, 30, 40, 50])
+ kernel = [0.5, 0.5]
+ actual = list(mi.convolve(signal, kernel))
+ expected = [
+ (10 + 0) / 2,
+ (20 + 10) / 2,
+ (30 + 20) / 2,
+ (40 + 30) / 2,
+ (50 + 40) / 2,
+ (0 + 50) / 2,
+ ]
+ self.assertEqual(actual, expected)
+
+ def test_derivative(self):
+ signal = iter([10, 20, 30, 40, 50])
+ kernel = [1, -1]
+ actual = list(mi.convolve(signal, kernel))
+ expected = [10 - 0, 20 - 10, 30 - 20, 40 - 30, 50 - 40, 0 - 50]
+ self.assertEqual(actual, expected)
+
+ def test_infinite_signal(self):
+ signal = count()
+ kernel = [1, -1]
+ actual = mi.take(5, mi.convolve(signal, kernel))
+ expected = [0, 1, 1, 1, 1]
+ self.assertEqual(actual, expected)