From fcb7dfa44168901a4c6f66d1a39d91c0fe60b297 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Ma=C5=9Blanka?= <piotr.maslanka@henrietta.com.pl>
Date: Sat, 25 Apr 2020 15:58:33 +0200
Subject: [PATCH] 2.7.13 - header of half_cartesian changed

---
 CHANGELOG.md                          | 2 +-
 docs/coding/sequences.rst             | 3 +++
 satella/__init__.py                   | 2 +-
 satella/coding/sequences/__init__.py  | 4 ++--
 satella/coding/sequences/sequences.py | 2 +-
 tests/test_coding/test_sequences.py   | 6 +++---
 6 files changed, 11 insertions(+), 8 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4da51ce8..d8217cca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
 # v2.7.13
 
-* _TBA_
+* bugfix release: header of `half_cartesian` changed
 
 # v2.7.12
 
diff --git a/docs/coding/sequences.rst b/docs/coding/sequences.rst
index e4edb510..00f877ee 100644
--- a/docs/coding/sequences.rst
+++ b/docs/coding/sequences.rst
@@ -59,6 +59,9 @@ if object A collides with B then B collides with A).
 
 It helps you save time during computationally intensive operations.
 
+This routine will return a iterator of tuple containing two elements
+from the same set (ie. it will do something like a cartesian power of two).
+
 .. autofuction:: satella.coding.sequences.half_cartesian
 
 skip_first
diff --git a/satella/__init__.py b/satella/__init__.py
index ea917150..83287f25 100644
--- a/satella/__init__.py
+++ b/satella/__init__.py
@@ -1 +1 @@
-__version__ = '2.7.13_a1'
+__version__ = '2.7.13'
diff --git a/satella/coding/sequences/__init__.py b/satella/coding/sequences/__init__.py
index 4030539f..51de7aef 100644
--- a/satella/coding/sequences/__init__.py
+++ b/satella/coding/sequences/__init__.py
@@ -1,9 +1,9 @@
 from .choose import choose
 from .iterators import infinite_counter, take_n, is_instance, skip_first, zip_shifted, stop_after, \
     iter_dict_of_list, shift, other_sequence_no_longer_than, count, even, odd
-from .sequences import is_last, add_next, half_product, group_quantity
+from .sequences import is_last, add_next, half_cartesian, group_quantity
 
 __all__ = ['choose', 'infinite_counter', 'take_n', 'is_instance', 'is_last', 'add_next',
-           'half_product', 'skip_first', 'zip_shifted', 'stop_after', 'group_quantity',
+           'half_cartesian', 'skip_first', 'zip_shifted', 'stop_after', 'group_quantity',
            'iter_dict_of_list', 'shift', 'other_sequence_no_longer_than', 'count',
            'even', 'odd']
diff --git a/satella/coding/sequences/sequences.py b/satella/coding/sequences/sequences.py
index 1de3dcde..b9cd7997 100644
--- a/satella/coding/sequences/sequences.py
+++ b/satella/coding/sequences/sequences.py
@@ -1 +1 @@
-import typing as tp

__all__ = ['is_last', 'add_next', 'half_product', 'group_quantity']
T = tp.TypeVar('T')
U = tp.TypeVar('U')


# shamelessly copied from https://medium.com/better-programming/is-this-the-last-element-of-my-python-for-loop-784f5ff90bb5
def is_last(lst: tp.Iterable[T]) -> tp.Generator[tp.Tuple[bool, T], None, None]:
    """
    Return every element of the list, alongside a flag telling is this the last element.

    Use like:

    >>> for is_last, element in is_last(my_list):
    >>> if is_last:
    >>>     ...

    :param lst: list to iterate thru
    :return: a generator returning (bool, T)
    """
    iterable = iter(lst)
    ret_var = next(iterable)
    for val in iterable:
        yield False, ret_var
        ret_var = val
    yield True, ret_var


def add_next(lst: tp.Iterable[T], wrap_over: bool = False, skip_last: bool = False) -> tp.Iterator[
        tp.Tuple[T, tp.Optional[T]]]:
    """
    Yields a 2-tuple of given iterable, presenting the next element as second element of the tuple.

    The last element will be the last element alongside with a None, if wrap_over is False, or the
    first element if wrap_over was True

    Example:

    >>> list(add_next([1, 2, 3, 4, 5])) == [(1, 2), (2, 3), (3, 4), (4, 5), (5, None)]
    >>> list(add_next([1, 2, 3, 4, 5], True)) == [(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]

    :param lst: iterable to iterate over
    :param wrap_over: whether to attach the first element to the pair of the last element instead
        of None
    :param skip_last: if this is True, then last element, alongside with a None, won't be output
    """
    iterator = iter(lst)
    try:
        first_val = prev_val = next(iterator)
    except StopIteration:
        return
    for val in iterator:
        yield prev_val, val
        prev_val = val
    if wrap_over:
        yield prev_val, first_val
    else:
        if not skip_last:
            yield prev_val, None


def half_product(seq1: tp.Iterable[T], seq2: tp.Iterable[U]) -> tp.Generator[
    tp.Tuple[T, U], None, None]:
    """
    Generate half of the Cartesian product of both sequences.

    Useful when you have a commutative operation that you'd like to execute on both elements
    (eg. checking for collisions).

    Example:

    >>> list(half_cartesian([1, 2, 3], [1, 2, 3])) == \
    >>>     [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]

    :param seq1: First sequence
    :param seq2: Second sequence
    """
    for i, elem1 in enumerate(seq1):
        for j, elem2 in enumerate(seq2):
            if j >= i:
                yield elem1, elem2


def group_quantity(length: int, seq: tp.Iterable[T]) -> tp.Generator[tp.List[T], None, None]:
    """
    Slice an iterable into lists containing at most len entries.

    Eg.

    >>> assert list(group_quantity(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) == [[1, 2, 3], [4, 5, 6],
    >>>                                                                     [7, 8, 9], [10]]

    :param length: length for the returning sequences
    :param seq: sequence to split
    """
    entries = []
    for elem in seq:
        if len(entries) < length:
            entries.append(elem)
        else:
            yield entries
            entries = [elem]
    if entries:
        yield entries
\ No newline at end of file
+import typing as tp

__all__ = ['is_last', 'add_next', 'half_cartesian', 'group_quantity']
T = tp.TypeVar('T')
U = tp.TypeVar('U')


# shamelessly copied from https://medium.com/better-programming/is-this-the-last-element-of-my-python-for-loop-784f5ff90bb5
def is_last(lst: tp.Iterable[T]) -> tp.Generator[tp.Tuple[bool, T], None, None]:
    """
    Return every element of the list, alongside a flag telling is this the last element.

    Use like:

    >>> for is_last, element in is_last(my_list):
    >>> if is_last:
    >>>     ...

    :param lst: list to iterate thru
    :return: a generator returning (bool, T)
    """
    iterable = iter(lst)
    ret_var = next(iterable)
    for val in iterable:
        yield False, ret_var
        ret_var = val
    yield True, ret_var


def add_next(lst: tp.Iterable[T], wrap_over: bool = False, skip_last: bool = False) -> tp.Iterator[
        tp.Tuple[T, tp.Optional[T]]]:
    """
    Yields a 2-tuple of given iterable, presenting the next element as second element of the tuple.

    The last element will be the last element alongside with a None, if wrap_over is False, or the
    first element if wrap_over was True

    Example:

    >>> list(add_next([1, 2, 3, 4, 5])) == [(1, 2), (2, 3), (3, 4), (4, 5), (5, None)]
    >>> list(add_next([1, 2, 3, 4, 5], True)) == [(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]

    :param lst: iterable to iterate over
    :param wrap_over: whether to attach the first element to the pair of the last element instead
        of None
    :param skip_last: if this is True, then last element, alongside with a None, won't be output
    """
    iterator = iter(lst)
    try:
        first_val = prev_val = next(iterator)
    except StopIteration:
        return
    for val in iterator:
        yield prev_val, val
        prev_val = val
    if wrap_over:
        yield prev_val, first_val
    else:
        if not skip_last:
            yield prev_val, None


def half_cartesian(seq1: tp.Iterable[T]) -> tp.Iterator[tp.Tuple[T, T]]:
    """
    Generate half of the Cartesian product of both sequences.

    Useful when you have a commutative operation that you'd like to execute on both elements
    (eg. checking for collisions).

    Example:

    >>> list(half_cartesian([1, 2, 3], [1, 2, 3])) == \
    >>>     [(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)]

    :param seq1: First sequence
    :param seq2: Second sequence
    """
    for i, elem1 in enumerate(seq1):
        for j, elem2 in enumerate(seq1):
            if j >= i:
                yield elem1, elem2


def group_quantity(length: int, seq: tp.Iterable[T]) -> tp.Generator[tp.List[T], None, None]:
    """
    Slice an iterable into lists containing at most len entries.

    Eg.

    >>> assert list(group_quantity(3, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) == [[1, 2, 3], [4, 5, 6],
    >>>                                                                     [7, 8, 9], [10]]

    :param length: length for the returning sequences
    :param seq: sequence to split
    """
    entries = []
    for elem in seq:
        if len(entries) < length:
            entries.append(elem)
        else:
            yield entries
            entries = [elem]
    if entries:
        yield entries
\ No newline at end of file
diff --git a/tests/test_coding/test_sequences.py b/tests/test_coding/test_sequences.py
index 753c0d8c..52a78532 100644
--- a/tests/test_coding/test_sequences.py
+++ b/tests/test_coding/test_sequences.py
@@ -2,7 +2,7 @@ import logging
 import unittest
 
 from satella.coding.sequences import choose, infinite_counter, take_n, is_instance, is_last, \
-    add_next, half_product, skip_first, zip_shifted, stop_after, group_quantity, \
+    add_next, half_cartesian, skip_first, zip_shifted, stop_after, group_quantity, \
     iter_dict_of_list, shift, other_sequence_no_longer_than, count, even, odd
 
 logger = logging.getLogger(__name__)
@@ -53,8 +53,8 @@ class TestSequences(unittest.TestCase):
         b = list(skip_first(a, 1))
         self.assertEqual(b, [2, 3, 4, 5])
 
-    def test_half_product(self):
-        a = set(half_product([1, 2, 3], [1, 2, 3]))
+    def test_half_cartesian(self):
+        a = set(half_cartesian([1, 2, 3], [1, 2, 3]))
         b = set([(1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (3, 3)])
         self.assertEqual(a, b)
 
-- 
GitLab