From bf5f28b6729605152ca2475569b97130f5354e48 Mon Sep 17 00:00:00 2001 From: Sangyub Lee Date: Fri, 30 Dec 2022 00:41:42 +0200 Subject: [PATCH 1/7] Decompose partition function from kbins --- sympy/utilities/iterables.py | 144 +++++++++++++++++++++--- sympy/utilities/tests/test_iterables.py | 51 ++++++++- 2 files changed, 179 insertions(+), 16 deletions(-) diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py index 144713d4fe47..0c929567522a 100644 --- a/sympy/utilities/iterables.py +++ b/sympy/utilities/iterables.py @@ -3,7 +3,7 @@ chain, combinations, combinations_with_replacement, cycle, islice, permutations, product ) - +from typing import TypeVar, Sequence, List, Iterator # For backwards compatibility from itertools import product as cartes # noqa: F401 from operator import gt @@ -2706,6 +2706,132 @@ def runs(seq, op=gt): return cycles +T = TypeVar('T') + + +def sequence_partitions(l: Sequence[T], n: int) -> Iterator[List[Sequence[T]]]: + r"""Returns the partition of sequence $l$ into $n$ bins + + Explanation + =========== + + Given the sequence $l_1 \cdots l_m \in V^+$ where + $V^+$ is the Kleene plus of $V$ + + The set of $n$ partitions of $l$ is defined as: + + .. math:: + \{(s_1, \cdots, s_n) | s_1 \in V^+, \cdots, s_n \in V^+, + s_1 \cdots s_n = l_1 \cdots l_m\} + + Parameters + ========== + + l + A nonempty sequence of any Python objects + + n + A positive integer + + Yields + ====== + + out + A list of sequences with concatenation equals $l$ + + Examples + ======== + + >>> from sympy.utilities.iterables import sequence_partitions + >>> for out in sequence_partitions([1, 2, 3, 4], 2): + ... print(out) + [[1], [2, 3, 4]] + [[1, 2], [3, 4]] + [[1, 2, 3], [4]] + + Notes + ===== + + This is modified version of EnricoGiampieri's partition generator + from https://stackoverflow.com/questions/13131491/ + partition-n-items-into-k-bins-in-python-lazily + + See Also + ======== + + sequence_partitions_empty + """ + # Asserting l is nonempty is done only for sanity check + if n == 1 and l: + yield [l] + return + for i in range(1, len(l)): + for part in sequence_partitions(l[i:], n - 1): + yield [l[:i]] + part + + +def sequence_partitions_empty(l: Sequence[T], n: int) -> Iterator[List[Sequence[T]]]: + r"""Returns the partition of sequence $l$ into $n$ bins with + empty sequence + + Explanation + =========== + + Given the sequence $l_1 \cdots l_m \in V^*$ where + $V^*$ is the Kleene star of $V$ + + The set of $n$ partitions of $l$ is defined as: + + .. math:: + \{(s_1, \cdots, s_n) | s_1 \in V^*, \cdots, s_n \in V^*, + s_1 \cdots s_n = l_1 \cdots l_m\} + + There are more combinations than :func:`sequence_partition` because + empty sequence can fill everywhere, so we try to provide different + utility for this. + + Parameters + ========== + + l + A sequence of any Python objects (can be possibly empty) + + n + A positive integer + + Yields + ====== + + out + A list of sequences with concatenation equals $l$ + + Examples + ======== + + >>> from sympy.utilities.iterables import sequence_partitions_empty + >>> for out in sequence_partitions_empty([1, 2, 3, 4], 2): + ... print(out) + [[], [1, 2, 3, 4]] + [[1], [2, 3, 4]] + [[1, 2], [3, 4]] + [[1, 2, 3], [4]] + [[1, 2, 3, 4], []] + + See Also + ======== + + sequence_partitions + """ + if n < 1: + return + if n == 1: + yield [l] + return + for i in range(0, len(l) + 1): + for part in sequence_partitions_empty(l[i:], n - 1): + yield [l[:i]] + part + + def kbins(l, k, ordered=None): """ Return sequence ``l`` partitioned into ``k`` bins. @@ -2787,24 +2913,12 @@ def kbins(l, k, ordered=None): partitions, multiset_partitions """ - def partition(lista, bins): - # EnricoGiampieri's partition generator from - # https://stackoverflow.com/questions/13131491/ - # partition-n-items-into-k-bins-in-python-lazily - if len(lista) == 1 or bins == 1: - yield [lista] - elif len(lista) > 1 and bins > 1: - for i in range(1, len(lista)): - for part in partition(lista[i:], bins - 1): - if len([lista[:i]] + part) == bins: - yield [lista[:i]] + part - if ordered is None: - yield from partition(l, k) + yield from sequence_partitions(l, k) elif ordered == 11: for pl in multiset_permutations(l): pl = list(pl) - yield from partition(pl, k) + yield from sequence_partitions(pl, k) elif ordered == 00: yield from multiset_partitions(l, k) elif ordered == 10: diff --git a/sympy/utilities/tests/test_iterables.py b/sympy/utilities/tests/test_iterables.py index 77c6e7b1ee80..8e527a447e75 100644 --- a/sympy/utilities/tests/test_iterables.py +++ b/sympy/utilities/tests/test_iterables.py @@ -19,7 +19,8 @@ prefixes, reshape, rotate_left, rotate_right, runs, sift, strongly_connected_components, subsets, take, topological_sort, unflatten, uniq, variations, ordered_partitions, rotations, is_palindromic, iterable, - NotIterable, multiset_derangements) + NotIterable, multiset_derangements, + sequence_partitions, sequence_partitions_empty) from sympy.utilities.enumerative import ( factoring_visitor, multiset_partitions_taocp ) @@ -885,3 +886,51 @@ class Test6(Test5): _iterable = False assert iterable(Test6()) is False + + +def test_sequence_partitions(): + assert list(sequence_partitions([1], 1)) == [[[1]]] + assert list(sequence_partitions([1, 2], 1)) == [[[1, 2]]] + assert list(sequence_partitions([1, 2], 2)) == [[[1], [2]]] + assert list(sequence_partitions([1, 2, 3], 1)) == [[[1, 2, 3]]] + assert list(sequence_partitions([1, 2, 3], 2)) == \ + [[[1], [2, 3]], [[1, 2], [3]]] + assert list(sequence_partitions([1, 2, 3], 3)) == [[[1], [2], [3]]] + + # Exceptional cases + assert list(sequence_partitions([], 0)) == [] + assert list(sequence_partitions([], 1)) == [] + assert list(sequence_partitions([1, 2], 0)) == [] + assert list(sequence_partitions([1, 2], 3)) == [] + + +def test_sequence_partitions_empty(): + assert list(sequence_partitions_empty([], 1)) == [[[]]] + assert list(sequence_partitions_empty([], 2)) == [[[], []]] + assert list(sequence_partitions_empty([], 3)) == [[[], [], []]] + assert list(sequence_partitions_empty([1], 1)) == [[[1]]] + assert list(sequence_partitions_empty([1], 2)) == [[[], [1]], [[1], []]] + assert list(sequence_partitions_empty([1], 3)) == \ + [[[], [], [1]], [[], [1], []], [[1], [], []]] + assert list(sequence_partitions_empty([1, 2], 1)) == [[[1, 2]]] + assert list(sequence_partitions_empty([1, 2], 2)) == \ + [[[], [1, 2]], [[1], [2]], [[1, 2], []]] + assert list(sequence_partitions_empty([1, 2], 3)) == [ + [[], [], [1, 2]], [[], [1], [2]], [[], [1, 2], []], + [[1], [], [2]], [[1], [2], []], [[1, 2], [], []] + ] + assert list(sequence_partitions_empty([1, 2, 3], 1)) == [[[1, 2, 3]]] + assert list(sequence_partitions_empty([1, 2, 3], 2)) == \ + [[[], [1, 2, 3]], [[1], [2, 3]], [[1, 2], [3]], [[1, 2, 3], []]] + assert list(sequence_partitions_empty([1, 2, 3], 3)) == [ + [[], [], [1, 2, 3]], [[], [1], [2, 3]], + [[], [1, 2], [3]], [[], [1, 2, 3], []], + [[1], [], [2, 3]], [[1], [2], [3]], + [[1], [2, 3], []], [[1, 2], [], [3]], + [[1, 2], [3], []], [[1, 2, 3], [], []] + ] + + # Exceptional cases + assert list(sequence_partitions([], 0)) == [] + assert list(sequence_partitions([1], 0)) == [] + assert list(sequence_partitions([1, 2], 0)) == [] From 6dc58eae99ab7f20dd2bb480d0cef0e2ad2ad384 Mon Sep 17 00:00:00 2001 From: Sangyub Lee Date: Fri, 30 Dec 2022 01:27:41 +0200 Subject: [PATCH 2/7] Attempt to fix reference --- sympy/utilities/iterables.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py index 0c929567522a..3db151be6e68 100644 --- a/sympy/utilities/iterables.py +++ b/sympy/utilities/iterables.py @@ -2706,10 +2706,10 @@ def runs(seq, op=gt): return cycles -T = TypeVar('T') +_T = TypeVar('_T') -def sequence_partitions(l: Sequence[T], n: int) -> Iterator[List[Sequence[T]]]: +def sequence_partitions(l: Sequence[_T], n: int) -> Iterator[List[Sequence[_T]]]: r"""Returns the partition of sequence $l$ into $n$ bins Explanation @@ -2770,7 +2770,7 @@ def sequence_partitions(l: Sequence[T], n: int) -> Iterator[List[Sequence[T]]]: yield [l[:i]] + part -def sequence_partitions_empty(l: Sequence[T], n: int) -> Iterator[List[Sequence[T]]]: +def sequence_partitions_empty(l: Sequence[_T], n: int) -> Iterator[List[Sequence[_T]]]: r"""Returns the partition of sequence $l$ into $n$ bins with empty sequence From 3ccb38961bc9d64967e6dc17676ca5d7e275fd98 Mon Sep 17 00:00:00 2001 From: Sangyub Lee Date: Fri, 30 Dec 2022 01:30:34 +0200 Subject: [PATCH 3/7] Shorter URL --- sympy/utilities/iterables.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py index 3db151be6e68..d8dfc637632e 100644 --- a/sympy/utilities/iterables.py +++ b/sympy/utilities/iterables.py @@ -2754,7 +2754,6 @@ def sequence_partitions(l: Sequence[_T], n: int) -> Iterator[List[Sequence[_T]]] This is modified version of EnricoGiampieri's partition generator from https://stackoverflow.com/questions/13131491/ - partition-n-items-into-k-bins-in-python-lazily See Also ======== From 5fc76b7c0ddae4407fda219ee9fb4dba0b689398 Mon Sep 17 00:00:00 2001 From: Sangyub Lee Date: Fri, 30 Dec 2022 01:32:23 +0200 Subject: [PATCH 4/7] Fix further broken links --- sympy/utilities/iterables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py index d8dfc637632e..42851e3cf0a3 100644 --- a/sympy/utilities/iterables.py +++ b/sympy/utilities/iterables.py @@ -2785,7 +2785,7 @@ def sequence_partitions_empty(l: Sequence[_T], n: int) -> Iterator[List[Sequence \{(s_1, \cdots, s_n) | s_1 \in V^*, \cdots, s_n \in V^*, s_1 \cdots s_n = l_1 \cdots l_m\} - There are more combinations than :func:`sequence_partition` because + There are more combinations than :func:`sequence_partitions` because empty sequence can fill everywhere, so we try to provide different utility for this. From 2dacdb60d07a862dc87751962179f265f239247c Mon Sep 17 00:00:00 2001 From: Sangyub Lee Date: Fri, 30 Dec 2022 11:04:14 +0200 Subject: [PATCH 5/7] Remove type hints and move into docs --- sympy/utilities/iterables.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py index 42851e3cf0a3..c40b6bd9cfe7 100644 --- a/sympy/utilities/iterables.py +++ b/sympy/utilities/iterables.py @@ -3,7 +3,6 @@ chain, combinations, combinations_with_replacement, cycle, islice, permutations, product ) -from typing import TypeVar, Sequence, List, Iterator # For backwards compatibility from itertools import product as cartes # noqa: F401 from operator import gt @@ -2706,10 +2705,7 @@ def runs(seq, op=gt): return cycles -_T = TypeVar('_T') - - -def sequence_partitions(l: Sequence[_T], n: int) -> Iterator[List[Sequence[_T]]]: +def sequence_partitions(l, n): r"""Returns the partition of sequence $l$ into $n$ bins Explanation @@ -2727,17 +2723,18 @@ def sequence_partitions(l: Sequence[_T], n: int) -> Iterator[List[Sequence[_T]]] Parameters ========== - l + l : Sequence[T] A nonempty sequence of any Python objects - n + n : int A positive integer Yields ====== - out - A list of sequences with concatenation equals $l$ + out : Sequence[T] + A list of sequences with concatenation equals $l$. + This should conform with the type of $l$. Examples ======== @@ -2769,7 +2766,7 @@ def sequence_partitions(l: Sequence[_T], n: int) -> Iterator[List[Sequence[_T]]] yield [l[:i]] + part -def sequence_partitions_empty(l: Sequence[_T], n: int) -> Iterator[List[Sequence[_T]]]: +def sequence_partitions_empty(l, n): r"""Returns the partition of sequence $l$ into $n$ bins with empty sequence @@ -2792,17 +2789,18 @@ def sequence_partitions_empty(l: Sequence[_T], n: int) -> Iterator[List[Sequence Parameters ========== - l + l : Sequence[T] A sequence of any Python objects (can be possibly empty) - n + n : int A positive integer Yields ====== - out - A list of sequences with concatenation equals $l$ + out : Sequence[T] + A list of sequences with concatenation equals $l$. + This should conform with the type of $l$. Examples ======== From aa1f06f6477e79b39dac834a0f084dd36e1aded2 Mon Sep 17 00:00:00 2001 From: Sangyub Lee Date: Fri, 30 Dec 2022 11:54:14 +0200 Subject: [PATCH 6/7] Mark list --- sympy/utilities/iterables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py index c40b6bd9cfe7..1e00ef918c67 100644 --- a/sympy/utilities/iterables.py +++ b/sympy/utilities/iterables.py @@ -2732,7 +2732,7 @@ def sequence_partitions(l, n): Yields ====== - out : Sequence[T] + out : list[Sequence[T]] A list of sequences with concatenation equals $l$. This should conform with the type of $l$. @@ -2798,7 +2798,7 @@ def sequence_partitions_empty(l, n): Yields ====== - out : Sequence[T] + out : list[Sequence[T]] A list of sequences with concatenation equals $l$. This should conform with the type of $l$. From fb163ae06c5181a5380c950fadfb887b76ec44c4 Mon Sep 17 00:00:00 2001 From: Sangyub Lee Date: Sat, 31 Dec 2022 11:54:12 +0200 Subject: [PATCH 7/7] Use positional argument --- sympy/utilities/iterables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sympy/utilities/iterables.py b/sympy/utilities/iterables.py index 1e00ef918c67..a0eb4cece751 100644 --- a/sympy/utilities/iterables.py +++ b/sympy/utilities/iterables.py @@ -2705,7 +2705,7 @@ def runs(seq, op=gt): return cycles -def sequence_partitions(l, n): +def sequence_partitions(l, n, /): r"""Returns the partition of sequence $l$ into $n$ bins Explanation @@ -2766,7 +2766,7 @@ def sequence_partitions(l, n): yield [l[:i]] + part -def sequence_partitions_empty(l, n): +def sequence_partitions_empty(l, n, /): r"""Returns the partition of sequence $l$ into $n$ bins with empty sequence