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 <dmuel...@suse.com>
+
+- 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)

Reply via email to