|
from collections import Counter, defaultdict, OrderedDict |
|
from itertools import ( |
|
chain, combinations, combinations_with_replacement, cycle, islice, |
|
permutations, product, groupby |
|
) |
|
|
|
from itertools import product as cartes |
|
from operator import gt |
|
|
|
|
|
|
|
|
|
from sympy.utilities.enumerative import ( |
|
multiset_partitions_taocp, list_visitor, MultisetPartitionTraverser) |
|
|
|
from sympy.utilities.misc import as_int |
|
from sympy.utilities.decorator import deprecated |
|
|
|
|
|
def is_palindromic(s, i=0, j=None): |
|
""" |
|
Return True if the sequence is the same from left to right as it |
|
is from right to left in the whole sequence (default) or in the |
|
Python slice ``s[i: j]``; else False. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import is_palindromic |
|
>>> is_palindromic([1, 0, 1]) |
|
True |
|
>>> is_palindromic('abcbb') |
|
False |
|
>>> is_palindromic('abcbb', 1) |
|
False |
|
|
|
Normal Python slicing is performed in place so there is no need to |
|
create a slice of the sequence for testing: |
|
|
|
>>> is_palindromic('abcbb', 1, -1) |
|
True |
|
>>> is_palindromic('abcbb', -4, -1) |
|
True |
|
|
|
See Also |
|
======== |
|
|
|
sympy.ntheory.digits.is_palindromic: tests integers |
|
|
|
""" |
|
i, j, _ = slice(i, j).indices(len(s)) |
|
m = (j - i)//2 |
|
|
|
return all(s[i + k] == s[j - 1 - k] for k in range(m)) |
|
|
|
|
|
def flatten(iterable, levels=None, cls=None): |
|
""" |
|
Recursively denest iterable containers. |
|
|
|
>>> from sympy import flatten |
|
|
|
>>> flatten([1, 2, 3]) |
|
[1, 2, 3] |
|
>>> flatten([1, 2, [3]]) |
|
[1, 2, 3] |
|
>>> flatten([1, [2, 3], [4, 5]]) |
|
[1, 2, 3, 4, 5] |
|
>>> flatten([1.0, 2, (1, None)]) |
|
[1.0, 2, 1, None] |
|
|
|
If you want to denest only a specified number of levels of |
|
nested containers, then set ``levels`` flag to the desired |
|
number of levels:: |
|
|
|
>>> ls = [[(-2, -1), (1, 2)], [(0, 0)]] |
|
|
|
>>> flatten(ls, levels=1) |
|
[(-2, -1), (1, 2), (0, 0)] |
|
|
|
If cls argument is specified, it will only flatten instances of that |
|
class, for example: |
|
|
|
>>> from sympy import Basic, S |
|
>>> class MyOp(Basic): |
|
... pass |
|
... |
|
>>> flatten([MyOp(S(1), MyOp(S(2), S(3)))], cls=MyOp) |
|
[1, 2, 3] |
|
|
|
adapted from https://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks |
|
""" |
|
from sympy.tensor.array import NDimArray |
|
if levels is not None: |
|
if not levels: |
|
return iterable |
|
elif levels > 0: |
|
levels -= 1 |
|
else: |
|
raise ValueError( |
|
"expected non-negative number of levels, got %s" % levels) |
|
|
|
if cls is None: |
|
def reducible(x): |
|
return is_sequence(x, set) |
|
else: |
|
def reducible(x): |
|
return isinstance(x, cls) |
|
|
|
result = [] |
|
|
|
for el in iterable: |
|
if reducible(el): |
|
if hasattr(el, 'args') and not isinstance(el, NDimArray): |
|
el = el.args |
|
result.extend(flatten(el, levels=levels, cls=cls)) |
|
else: |
|
result.append(el) |
|
|
|
return result |
|
|
|
|
|
def unflatten(iter, n=2): |
|
"""Group ``iter`` into tuples of length ``n``. Raise an error if |
|
the length of ``iter`` is not a multiple of ``n``. |
|
""" |
|
if n < 1 or len(iter) % n: |
|
raise ValueError('iter length is not a multiple of %i' % n) |
|
return list(zip(*(iter[i::n] for i in range(n)))) |
|
|
|
|
|
def reshape(seq, how): |
|
"""Reshape the sequence according to the template in ``how``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities import reshape |
|
>>> seq = list(range(1, 9)) |
|
|
|
>>> reshape(seq, [4]) # lists of 4 |
|
[[1, 2, 3, 4], [5, 6, 7, 8]] |
|
|
|
>>> reshape(seq, (4,)) # tuples of 4 |
|
[(1, 2, 3, 4), (5, 6, 7, 8)] |
|
|
|
>>> reshape(seq, (2, 2)) # tuples of 4 |
|
[(1, 2, 3, 4), (5, 6, 7, 8)] |
|
|
|
>>> reshape(seq, (2, [2])) # (i, i, [i, i]) |
|
[(1, 2, [3, 4]), (5, 6, [7, 8])] |
|
|
|
>>> reshape(seq, ((2,), [2])) # etc.... |
|
[((1, 2), [3, 4]), ((5, 6), [7, 8])] |
|
|
|
>>> reshape(seq, (1, [2], 1)) |
|
[(1, [2, 3], 4), (5, [6, 7], 8)] |
|
|
|
>>> reshape(tuple(seq), ([[1], 1, (2,)],)) |
|
(([[1], 2, (3, 4)],), ([[5], 6, (7, 8)],)) |
|
|
|
>>> reshape(tuple(seq), ([1], 1, (2,))) |
|
(([1], 2, (3, 4)), ([5], 6, (7, 8))) |
|
|
|
>>> reshape(list(range(12)), [2, [3], {2}, (1, (3,), 1)]) |
|
[[0, 1, [2, 3, 4], {5, 6}, (7, (8, 9, 10), 11)]] |
|
|
|
""" |
|
m = sum(flatten(how)) |
|
n, rem = divmod(len(seq), m) |
|
if m < 0 or rem: |
|
raise ValueError('template must sum to positive number ' |
|
'that divides the length of the sequence') |
|
i = 0 |
|
container = type(how) |
|
rv = [None]*n |
|
for k in range(len(rv)): |
|
_rv = [] |
|
for hi in how: |
|
if isinstance(hi, int): |
|
_rv.extend(seq[i: i + hi]) |
|
i += hi |
|
else: |
|
n = sum(flatten(hi)) |
|
hi_type = type(hi) |
|
_rv.append(hi_type(reshape(seq[i: i + n], hi)[0])) |
|
i += n |
|
rv[k] = container(_rv) |
|
return type(seq)(rv) |
|
|
|
|
|
def group(seq, multiple=True): |
|
""" |
|
Splits a sequence into a list of lists of equal, adjacent elements. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import group |
|
|
|
>>> group([1, 1, 1, 2, 2, 3]) |
|
[[1, 1, 1], [2, 2], [3]] |
|
>>> group([1, 1, 1, 2, 2, 3], multiple=False) |
|
[(1, 3), (2, 2), (3, 1)] |
|
>>> group([1, 1, 3, 2, 2, 1], multiple=False) |
|
[(1, 2), (3, 1), (2, 2), (1, 1)] |
|
|
|
See Also |
|
======== |
|
|
|
multiset |
|
|
|
""" |
|
if multiple: |
|
return [(list(g)) for _, g in groupby(seq)] |
|
return [(k, len(list(g))) for k, g in groupby(seq)] |
|
|
|
|
|
def _iproduct2(iterable1, iterable2): |
|
'''Cartesian product of two possibly infinite iterables''' |
|
|
|
it1 = iter(iterable1) |
|
it2 = iter(iterable2) |
|
|
|
elems1 = [] |
|
elems2 = [] |
|
|
|
sentinel = object() |
|
def append(it, elems): |
|
e = next(it, sentinel) |
|
if e is not sentinel: |
|
elems.append(e) |
|
|
|
n = 0 |
|
append(it1, elems1) |
|
append(it2, elems2) |
|
|
|
while n <= len(elems1) + len(elems2): |
|
for m in range(n-len(elems1)+1, len(elems2)): |
|
yield (elems1[n-m], elems2[m]) |
|
n += 1 |
|
append(it1, elems1) |
|
append(it2, elems2) |
|
|
|
|
|
def iproduct(*iterables): |
|
''' |
|
Cartesian product of iterables. |
|
|
|
Generator of the Cartesian product of iterables. This is analogous to |
|
itertools.product except that it works with infinite iterables and will |
|
yield any item from the infinite product eventually. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import iproduct |
|
>>> sorted(iproduct([1,2], [3,4])) |
|
[(1, 3), (1, 4), (2, 3), (2, 4)] |
|
|
|
With an infinite iterator: |
|
|
|
>>> from sympy import S |
|
>>> (3,) in iproduct(S.Integers) |
|
True |
|
>>> (3, 4) in iproduct(S.Integers, S.Integers) |
|
True |
|
|
|
.. seealso:: |
|
|
|
`itertools.product |
|
<https://docs.python.org/3/library/itertools.html#itertools.product>`_ |
|
''' |
|
if len(iterables) == 0: |
|
yield () |
|
return |
|
elif len(iterables) == 1: |
|
for e in iterables[0]: |
|
yield (e,) |
|
elif len(iterables) == 2: |
|
yield from _iproduct2(*iterables) |
|
else: |
|
first, others = iterables[0], iterables[1:] |
|
for ef, eo in _iproduct2(first, iproduct(*others)): |
|
yield (ef,) + eo |
|
|
|
|
|
def multiset(seq): |
|
"""Return the hashable sequence in multiset form with values being the |
|
multiplicity of the item in the sequence. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import multiset |
|
>>> multiset('mississippi') |
|
{'i': 4, 'm': 1, 'p': 2, 's': 4} |
|
|
|
See Also |
|
======== |
|
|
|
group |
|
|
|
""" |
|
return dict(Counter(seq).items()) |
|
|
|
|
|
|
|
|
|
def ibin(n, bits=None, str=False): |
|
"""Return a list of length ``bits`` corresponding to the binary value |
|
of ``n`` with small bits to the right (last). If bits is omitted, the |
|
length will be the number required to represent ``n``. If the bits are |
|
desired in reversed order, use the ``[::-1]`` slice of the returned list. |
|
|
|
If a sequence of all bits-length lists starting from ``[0, 0,..., 0]`` |
|
through ``[1, 1, ..., 1]`` are desired, pass a non-integer for bits, e.g. |
|
``'all'``. |
|
|
|
If the bit *string* is desired pass ``str=True``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import ibin |
|
>>> ibin(2) |
|
[1, 0] |
|
>>> ibin(2, 4) |
|
[0, 0, 1, 0] |
|
|
|
If all lists corresponding to 0 to 2**n - 1, pass a non-integer |
|
for bits: |
|
|
|
>>> bits = 2 |
|
>>> for i in ibin(2, 'all'): |
|
... print(i) |
|
(0, 0) |
|
(0, 1) |
|
(1, 0) |
|
(1, 1) |
|
|
|
If a bit string is desired of a given length, use str=True: |
|
|
|
>>> n = 123 |
|
>>> bits = 10 |
|
>>> ibin(n, bits, str=True) |
|
'0001111011' |
|
>>> ibin(n, bits, str=True)[::-1] # small bits left |
|
'1101111000' |
|
>>> list(ibin(3, 'all', str=True)) |
|
['000', '001', '010', '011', '100', '101', '110', '111'] |
|
|
|
""" |
|
if n < 0: |
|
raise ValueError("negative numbers are not allowed") |
|
n = as_int(n) |
|
|
|
if bits is None: |
|
bits = 0 |
|
else: |
|
try: |
|
bits = as_int(bits) |
|
except ValueError: |
|
bits = -1 |
|
else: |
|
if n.bit_length() > bits: |
|
raise ValueError( |
|
"`bits` must be >= {}".format(n.bit_length())) |
|
|
|
if not str: |
|
if bits >= 0: |
|
return [1 if i == "1" else 0 for i in bin(n)[2:].rjust(bits, "0")] |
|
else: |
|
return variations(range(2), n, repetition=True) |
|
else: |
|
if bits >= 0: |
|
return bin(n)[2:].rjust(bits, "0") |
|
else: |
|
return (bin(i)[2:].rjust(n, "0") for i in range(2**n)) |
|
|
|
|
|
def variations(seq, n, repetition=False): |
|
r"""Returns an iterator over the n-sized variations of ``seq`` (size N). |
|
``repetition`` controls whether items in ``seq`` can appear more than once; |
|
|
|
Examples |
|
======== |
|
|
|
``variations(seq, n)`` will return `\frac{N!}{(N - n)!}` permutations without |
|
repetition of ``seq``'s elements: |
|
|
|
>>> from sympy import variations |
|
>>> list(variations([1, 2], 2)) |
|
[(1, 2), (2, 1)] |
|
|
|
``variations(seq, n, True)`` will return the `N^n` permutations obtained |
|
by allowing repetition of elements: |
|
|
|
>>> list(variations([1, 2], 2, repetition=True)) |
|
[(1, 1), (1, 2), (2, 1), (2, 2)] |
|
|
|
If you ask for more items than are in the set you get the empty set unless |
|
you allow repetitions: |
|
|
|
>>> list(variations([0, 1], 3, repetition=False)) |
|
[] |
|
>>> list(variations([0, 1], 3, repetition=True))[:4] |
|
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1)] |
|
|
|
.. seealso:: |
|
|
|
`itertools.permutations |
|
<https://docs.python.org/3/library/itertools.html#itertools.permutations>`_, |
|
`itertools.product |
|
<https://docs.python.org/3/library/itertools.html#itertools.product>`_ |
|
""" |
|
if not repetition: |
|
seq = tuple(seq) |
|
if len(seq) < n: |
|
return iter(()) |
|
return permutations(seq, n) |
|
else: |
|
if n == 0: |
|
return iter(((),)) |
|
else: |
|
return product(seq, repeat=n) |
|
|
|
|
|
def subsets(seq, k=None, repetition=False): |
|
r"""Generates all `k`-subsets (combinations) from an `n`-element set, ``seq``. |
|
|
|
A `k`-subset of an `n`-element set is any subset of length exactly `k`. The |
|
number of `k`-subsets of an `n`-element set is given by ``binomial(n, k)``, |
|
whereas there are `2^n` subsets all together. If `k` is ``None`` then all |
|
`2^n` subsets will be returned from shortest to longest. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import subsets |
|
|
|
``subsets(seq, k)`` will return the |
|
`\frac{n!}{k!(n - k)!}` `k`-subsets (combinations) |
|
without repetition, i.e. once an item has been removed, it can no |
|
longer be "taken": |
|
|
|
>>> list(subsets([1, 2], 2)) |
|
[(1, 2)] |
|
>>> list(subsets([1, 2])) |
|
[(), (1,), (2,), (1, 2)] |
|
>>> list(subsets([1, 2, 3], 2)) |
|
[(1, 2), (1, 3), (2, 3)] |
|
|
|
|
|
``subsets(seq, k, repetition=True)`` will return the |
|
`\frac{(n - 1 + k)!}{k!(n - 1)!}` |
|
combinations *with* repetition: |
|
|
|
>>> list(subsets([1, 2], 2, repetition=True)) |
|
[(1, 1), (1, 2), (2, 2)] |
|
|
|
If you ask for more items than are in the set you get the empty set unless |
|
you allow repetitions: |
|
|
|
>>> list(subsets([0, 1], 3, repetition=False)) |
|
[] |
|
>>> list(subsets([0, 1], 3, repetition=True)) |
|
[(0, 0, 0), (0, 0, 1), (0, 1, 1), (1, 1, 1)] |
|
|
|
""" |
|
if k is None: |
|
if not repetition: |
|
return chain.from_iterable((combinations(seq, k) |
|
for k in range(len(seq) + 1))) |
|
else: |
|
return chain.from_iterable((combinations_with_replacement(seq, k) |
|
for k in range(len(seq) + 1))) |
|
else: |
|
if not repetition: |
|
return combinations(seq, k) |
|
else: |
|
return combinations_with_replacement(seq, k) |
|
|
|
|
|
def filter_symbols(iterator, exclude): |
|
""" |
|
Only yield elements from `iterator` that do not occur in `exclude`. |
|
|
|
Parameters |
|
========== |
|
|
|
iterator : iterable |
|
iterator to take elements from |
|
|
|
exclude : iterable |
|
elements to exclude |
|
|
|
Returns |
|
======= |
|
|
|
iterator : iterator |
|
filtered iterator |
|
""" |
|
exclude = set(exclude) |
|
for s in iterator: |
|
if s not in exclude: |
|
yield s |
|
|
|
def numbered_symbols(prefix='x', cls=None, start=0, exclude=(), *args, **assumptions): |
|
""" |
|
Generate an infinite stream of Symbols consisting of a prefix and |
|
increasing subscripts provided that they do not occur in ``exclude``. |
|
|
|
Parameters |
|
========== |
|
|
|
prefix : str, optional |
|
The prefix to use. By default, this function will generate symbols of |
|
the form "x0", "x1", etc. |
|
|
|
cls : class, optional |
|
The class to use. By default, it uses ``Symbol``, but you can also use ``Wild`` |
|
or ``Dummy``. |
|
|
|
start : int, optional |
|
The start number. By default, it is 0. |
|
|
|
exclude : list, tuple, set of cls, optional |
|
Symbols to be excluded. |
|
|
|
*args, **kwargs |
|
Additional positional and keyword arguments are passed to the *cls* class. |
|
|
|
Returns |
|
======= |
|
|
|
sym : Symbol |
|
The subscripted symbols. |
|
""" |
|
exclude = set(exclude or []) |
|
if cls is None: |
|
|
|
|
|
from sympy.core import Symbol |
|
cls = Symbol |
|
|
|
while True: |
|
name = '%s%s' % (prefix, start) |
|
s = cls(name, *args, **assumptions) |
|
if s not in exclude: |
|
yield s |
|
start += 1 |
|
|
|
|
|
def capture(func): |
|
"""Return the printed output of func(). |
|
|
|
``func`` should be a function without arguments that produces output with |
|
print statements. |
|
|
|
>>> from sympy.utilities.iterables import capture |
|
>>> from sympy import pprint |
|
>>> from sympy.abc import x |
|
>>> def foo(): |
|
... print('hello world!') |
|
... |
|
>>> 'hello' in capture(foo) # foo, not foo() |
|
True |
|
>>> capture(lambda: pprint(2/x)) |
|
'2\\n-\\nx\\n' |
|
|
|
""" |
|
from io import StringIO |
|
import sys |
|
|
|
stdout = sys.stdout |
|
sys.stdout = file = StringIO() |
|
try: |
|
func() |
|
finally: |
|
sys.stdout = stdout |
|
return file.getvalue() |
|
|
|
|
|
def sift(seq, keyfunc, binary=False): |
|
""" |
|
Sift the sequence, ``seq`` according to ``keyfunc``. |
|
|
|
Returns |
|
======= |
|
|
|
When ``binary`` is ``False`` (default), the output is a dictionary |
|
where elements of ``seq`` are stored in a list keyed to the value |
|
of keyfunc for that element. If ``binary`` is True then a tuple |
|
with lists ``T`` and ``F`` are returned where ``T`` is a list |
|
containing elements of seq for which ``keyfunc`` was ``True`` and |
|
``F`` containing those elements for which ``keyfunc`` was ``False``; |
|
a ValueError is raised if the ``keyfunc`` is not binary. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities import sift |
|
>>> from sympy.abc import x, y |
|
>>> from sympy import sqrt, exp, pi, Tuple |
|
|
|
>>> sift(range(5), lambda x: x % 2) |
|
{0: [0, 2, 4], 1: [1, 3]} |
|
|
|
sift() returns a defaultdict() object, so any key that has no matches will |
|
give []. |
|
|
|
>>> sift([x], lambda x: x.is_commutative) |
|
{True: [x]} |
|
>>> _[False] |
|
[] |
|
|
|
Sometimes you will not know how many keys you will get: |
|
|
|
>>> sift([sqrt(x), exp(x), (y**x)**2], |
|
... lambda x: x.as_base_exp()[0]) |
|
{E: [exp(x)], x: [sqrt(x)], y: [y**(2*x)]} |
|
|
|
Sometimes you expect the results to be binary; the |
|
results can be unpacked by setting ``binary`` to True: |
|
|
|
>>> sift(range(4), lambda x: x % 2, binary=True) |
|
([1, 3], [0, 2]) |
|
>>> sift(Tuple(1, pi), lambda x: x.is_rational, binary=True) |
|
([1], [pi]) |
|
|
|
A ValueError is raised if the predicate was not actually binary |
|
(which is a good test for the logic where sifting is used and |
|
binary results were expected): |
|
|
|
>>> unknown = exp(1) - pi # the rationality of this is unknown |
|
>>> args = Tuple(1, pi, unknown) |
|
>>> sift(args, lambda x: x.is_rational, binary=True) |
|
Traceback (most recent call last): |
|
... |
|
ValueError: keyfunc gave non-binary output |
|
|
|
The non-binary sifting shows that there were 3 keys generated: |
|
|
|
>>> set(sift(args, lambda x: x.is_rational).keys()) |
|
{None, False, True} |
|
|
|
If you need to sort the sifted items it might be better to use |
|
``ordered`` which can economically apply multiple sort keys |
|
to a sequence while sorting. |
|
|
|
See Also |
|
======== |
|
|
|
ordered |
|
|
|
""" |
|
if not binary: |
|
m = defaultdict(list) |
|
for i in seq: |
|
m[keyfunc(i)].append(i) |
|
return m |
|
sift = F, T = [], [] |
|
for i in seq: |
|
try: |
|
sift[keyfunc(i)].append(i) |
|
except (IndexError, TypeError): |
|
raise ValueError('keyfunc gave non-binary output') |
|
return T, F |
|
|
|
|
|
def take(iter, n): |
|
"""Return ``n`` items from ``iter`` iterator. """ |
|
return [ value for _, value in zip(range(n), iter) ] |
|
|
|
|
|
def dict_merge(*dicts): |
|
"""Merge dictionaries into a single dictionary. """ |
|
merged = {} |
|
|
|
for dict in dicts: |
|
merged.update(dict) |
|
|
|
return merged |
|
|
|
|
|
def common_prefix(*seqs): |
|
"""Return the subsequence that is a common start of sequences in ``seqs``. |
|
|
|
>>> from sympy.utilities.iterables import common_prefix |
|
>>> common_prefix(list(range(3))) |
|
[0, 1, 2] |
|
>>> common_prefix(list(range(3)), list(range(4))) |
|
[0, 1, 2] |
|
>>> common_prefix([1, 2, 3], [1, 2, 5]) |
|
[1, 2] |
|
>>> common_prefix([1, 2, 3], [1, 3, 5]) |
|
[1] |
|
""" |
|
if not all(seqs): |
|
return [] |
|
elif len(seqs) == 1: |
|
return seqs[0] |
|
i = 0 |
|
for i in range(min(len(s) for s in seqs)): |
|
if not all(seqs[j][i] == seqs[0][i] for j in range(len(seqs))): |
|
break |
|
else: |
|
i += 1 |
|
return seqs[0][:i] |
|
|
|
|
|
def common_suffix(*seqs): |
|
"""Return the subsequence that is a common ending of sequences in ``seqs``. |
|
|
|
>>> from sympy.utilities.iterables import common_suffix |
|
>>> common_suffix(list(range(3))) |
|
[0, 1, 2] |
|
>>> common_suffix(list(range(3)), list(range(4))) |
|
[] |
|
>>> common_suffix([1, 2, 3], [9, 2, 3]) |
|
[2, 3] |
|
>>> common_suffix([1, 2, 3], [9, 7, 3]) |
|
[3] |
|
""" |
|
|
|
if not all(seqs): |
|
return [] |
|
elif len(seqs) == 1: |
|
return seqs[0] |
|
i = 0 |
|
for i in range(-1, -min(len(s) for s in seqs) - 1, -1): |
|
if not all(seqs[j][i] == seqs[0][i] for j in range(len(seqs))): |
|
break |
|
else: |
|
i -= 1 |
|
if i == -1: |
|
return [] |
|
else: |
|
return seqs[0][i + 1:] |
|
|
|
|
|
def prefixes(seq): |
|
""" |
|
Generate all prefixes of a sequence. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import prefixes |
|
|
|
>>> list(prefixes([1,2,3,4])) |
|
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4]] |
|
|
|
""" |
|
n = len(seq) |
|
|
|
for i in range(n): |
|
yield seq[:i + 1] |
|
|
|
|
|
def postfixes(seq): |
|
""" |
|
Generate all postfixes of a sequence. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import postfixes |
|
|
|
>>> list(postfixes([1,2,3,4])) |
|
[[4], [3, 4], [2, 3, 4], [1, 2, 3, 4]] |
|
|
|
""" |
|
n = len(seq) |
|
|
|
for i in range(n): |
|
yield seq[n - i - 1:] |
|
|
|
|
|
def topological_sort(graph, key=None): |
|
r""" |
|
Topological sort of graph's vertices. |
|
|
|
Parameters |
|
========== |
|
|
|
graph : tuple[list, list[tuple[T, T]] |
|
A tuple consisting of a list of vertices and a list of edges of |
|
a graph to be sorted topologically. |
|
|
|
key : callable[T] (optional) |
|
Ordering key for vertices on the same level. By default the natural |
|
(e.g. lexicographic) ordering is used (in this case the base type |
|
must implement ordering relations). |
|
|
|
Examples |
|
======== |
|
|
|
Consider a graph:: |
|
|
|
+---+ +---+ +---+ |
|
| 7 |\ | 5 | | 3 | |
|
+---+ \ +---+ +---+ |
|
| _\___/ ____ _/ | |
|
| / \___/ \ / | |
|
V V V V | |
|
+----+ +---+ | |
|
| 11 | | 8 | | |
|
+----+ +---+ | |
|
| | \____ ___/ _ | |
|
| \ \ / / \ | |
|
V \ V V / V V |
|
+---+ \ +---+ | +----+ |
|
| 2 | | | 9 | | | 10 | |
|
+---+ | +---+ | +----+ |
|
\________/ |
|
|
|
where vertices are integers. This graph can be encoded using |
|
elementary Python's data structures as follows:: |
|
|
|
>>> V = [2, 3, 5, 7, 8, 9, 10, 11] |
|
>>> E = [(7, 11), (7, 8), (5, 11), (3, 8), (3, 10), |
|
... (11, 2), (11, 9), (11, 10), (8, 9)] |
|
|
|
To compute a topological sort for graph ``(V, E)`` issue:: |
|
|
|
>>> from sympy.utilities.iterables import topological_sort |
|
|
|
>>> topological_sort((V, E)) |
|
[3, 5, 7, 8, 11, 2, 9, 10] |
|
|
|
If specific tie breaking approach is needed, use ``key`` parameter:: |
|
|
|
>>> topological_sort((V, E), key=lambda v: -v) |
|
[7, 5, 11, 3, 10, 8, 9, 2] |
|
|
|
Only acyclic graphs can be sorted. If the input graph has a cycle, |
|
then ``ValueError`` will be raised:: |
|
|
|
>>> topological_sort((V, E + [(10, 7)])) |
|
Traceback (most recent call last): |
|
... |
|
ValueError: cycle detected |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://en.wikipedia.org/wiki/Topological_sorting |
|
|
|
""" |
|
V, E = graph |
|
|
|
L = [] |
|
S = set(V) |
|
E = list(E) |
|
|
|
S.difference_update(u for v, u in E) |
|
|
|
if key is None: |
|
def key(value): |
|
return value |
|
|
|
S = sorted(S, key=key, reverse=True) |
|
|
|
while S: |
|
node = S.pop() |
|
L.append(node) |
|
|
|
for u, v in list(E): |
|
if u == node: |
|
E.remove((u, v)) |
|
|
|
for _u, _v in E: |
|
if v == _v: |
|
break |
|
else: |
|
kv = key(v) |
|
|
|
for i, s in enumerate(S): |
|
ks = key(s) |
|
|
|
if kv > ks: |
|
S.insert(i, v) |
|
break |
|
else: |
|
S.append(v) |
|
|
|
if E: |
|
raise ValueError("cycle detected") |
|
else: |
|
return L |
|
|
|
|
|
def strongly_connected_components(G): |
|
r""" |
|
Strongly connected components of a directed graph in reverse topological |
|
order. |
|
|
|
|
|
Parameters |
|
========== |
|
|
|
G : tuple[list, list[tuple[T, T]] |
|
A tuple consisting of a list of vertices and a list of edges of |
|
a graph whose strongly connected components are to be found. |
|
|
|
|
|
Examples |
|
======== |
|
|
|
Consider a directed graph (in dot notation):: |
|
|
|
digraph { |
|
A -> B |
|
A -> C |
|
B -> C |
|
C -> B |
|
B -> D |
|
} |
|
|
|
.. graphviz:: |
|
|
|
digraph { |
|
A -> B |
|
A -> C |
|
B -> C |
|
C -> B |
|
B -> D |
|
} |
|
|
|
where vertices are the letters A, B, C and D. This graph can be encoded |
|
using Python's elementary data structures as follows:: |
|
|
|
>>> V = ['A', 'B', 'C', 'D'] |
|
>>> E = [('A', 'B'), ('A', 'C'), ('B', 'C'), ('C', 'B'), ('B', 'D')] |
|
|
|
The strongly connected components of this graph can be computed as |
|
|
|
>>> from sympy.utilities.iterables import strongly_connected_components |
|
|
|
>>> strongly_connected_components((V, E)) |
|
[['D'], ['B', 'C'], ['A']] |
|
|
|
This also gives the components in reverse topological order. |
|
|
|
Since the subgraph containing B and C has a cycle they must be together in |
|
a strongly connected component. A and D are connected to the rest of the |
|
graph but not in a cyclic manner so they appear as their own strongly |
|
connected components. |
|
|
|
|
|
Notes |
|
===== |
|
|
|
The vertices of the graph must be hashable for the data structures used. |
|
If the vertices are unhashable replace them with integer indices. |
|
|
|
This function uses Tarjan's algorithm to compute the strongly connected |
|
components in `O(|V|+|E|)` (linear) time. |
|
|
|
|
|
References |
|
========== |
|
|
|
.. [1] https://en.wikipedia.org/wiki/Strongly_connected_component |
|
.. [2] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm |
|
|
|
|
|
See Also |
|
======== |
|
|
|
sympy.utilities.iterables.connected_components |
|
|
|
""" |
|
|
|
V, E = G |
|
Gmap = {vi: [] for vi in V} |
|
for v1, v2 in E: |
|
Gmap[v1].append(v2) |
|
return _strongly_connected_components(V, Gmap) |
|
|
|
|
|
def _strongly_connected_components(V, Gmap): |
|
"""More efficient internal routine for strongly_connected_components""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lowlink = {} |
|
indices = {} |
|
stack = OrderedDict() |
|
callstack = [] |
|
components = [] |
|
nomore = object() |
|
|
|
def start(v): |
|
index = len(stack) |
|
indices[v] = lowlink[v] = index |
|
stack[v] = None |
|
callstack.append((v, iter(Gmap[v]))) |
|
|
|
def finish(v1): |
|
|
|
if lowlink[v1] == indices[v1]: |
|
component = [stack.popitem()[0]] |
|
while component[-1] is not v1: |
|
component.append(stack.popitem()[0]) |
|
components.append(component[::-1]) |
|
v2, _ = callstack.pop() |
|
if callstack: |
|
v1, _ = callstack[-1] |
|
lowlink[v1] = min(lowlink[v1], lowlink[v2]) |
|
|
|
for v in V: |
|
if v in indices: |
|
continue |
|
start(v) |
|
while callstack: |
|
v1, it1 = callstack[-1] |
|
v2 = next(it1, nomore) |
|
|
|
if v2 is nomore: |
|
finish(v1) |
|
|
|
elif v2 not in indices: |
|
start(v2) |
|
elif v2 in stack: |
|
lowlink[v1] = min(lowlink[v1], indices[v2]) |
|
|
|
|
|
return components |
|
|
|
|
|
def connected_components(G): |
|
r""" |
|
Connected components of an undirected graph or weakly connected components |
|
of a directed graph. |
|
|
|
|
|
Parameters |
|
========== |
|
|
|
G : tuple[list, list[tuple[T, T]] |
|
A tuple consisting of a list of vertices and a list of edges of |
|
a graph whose connected components are to be found. |
|
|
|
|
|
Examples |
|
======== |
|
|
|
|
|
Given an undirected graph:: |
|
|
|
graph { |
|
A -- B |
|
C -- D |
|
} |
|
|
|
.. graphviz:: |
|
|
|
graph { |
|
A -- B |
|
C -- D |
|
} |
|
|
|
We can find the connected components using this function if we include |
|
each edge in both directions:: |
|
|
|
>>> from sympy.utilities.iterables import connected_components |
|
|
|
>>> V = ['A', 'B', 'C', 'D'] |
|
>>> E = [('A', 'B'), ('B', 'A'), ('C', 'D'), ('D', 'C')] |
|
>>> connected_components((V, E)) |
|
[['A', 'B'], ['C', 'D']] |
|
|
|
The weakly connected components of a directed graph can found the same |
|
way. |
|
|
|
|
|
Notes |
|
===== |
|
|
|
The vertices of the graph must be hashable for the data structures used. |
|
If the vertices are unhashable replace them with integer indices. |
|
|
|
This function uses Tarjan's algorithm to compute the connected components |
|
in `O(|V|+|E|)` (linear) time. |
|
|
|
|
|
References |
|
========== |
|
|
|
.. [1] https://en.wikipedia.org/wiki/Component_%28graph_theory%29 |
|
.. [2] https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm |
|
|
|
|
|
See Also |
|
======== |
|
|
|
sympy.utilities.iterables.strongly_connected_components |
|
|
|
""" |
|
|
|
|
|
V, E = G |
|
E_undirected = [] |
|
for v1, v2 in E: |
|
E_undirected.extend([(v1, v2), (v2, v1)]) |
|
return strongly_connected_components((V, E_undirected)) |
|
|
|
|
|
def rotate_left(x, y): |
|
""" |
|
Left rotates a list x by the number of steps specified |
|
in y. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import rotate_left |
|
>>> a = [0, 1, 2] |
|
>>> rotate_left(a, 1) |
|
[1, 2, 0] |
|
""" |
|
if len(x) == 0: |
|
return [] |
|
y = y % len(x) |
|
return x[y:] + x[:y] |
|
|
|
|
|
def rotate_right(x, y): |
|
""" |
|
Right rotates a list x by the number of steps specified |
|
in y. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import rotate_right |
|
>>> a = [0, 1, 2] |
|
>>> rotate_right(a, 1) |
|
[2, 0, 1] |
|
""" |
|
if len(x) == 0: |
|
return [] |
|
y = len(x) - y % len(x) |
|
return x[y:] + x[:y] |
|
|
|
|
|
def least_rotation(x, key=None): |
|
''' |
|
Returns the number of steps of left rotation required to |
|
obtain lexicographically minimal string/list/tuple, etc. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import least_rotation, rotate_left |
|
>>> a = [3, 1, 5, 1, 2] |
|
>>> least_rotation(a) |
|
3 |
|
>>> rotate_left(a, _) |
|
[1, 2, 3, 1, 5] |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://en.wikipedia.org/wiki/Lexicographically_minimal_string_rotation |
|
|
|
''' |
|
from sympy.functions.elementary.miscellaneous import Id |
|
if key is None: key = Id |
|
S = x + x |
|
f = [-1] * len(S) |
|
k = 0 |
|
for j in range(1,len(S)): |
|
sj = S[j] |
|
i = f[j-k-1] |
|
while i != -1 and sj != S[k+i+1]: |
|
if key(sj) < key(S[k+i+1]): |
|
k = j-i-1 |
|
i = f[i] |
|
if sj != S[k+i+1]: |
|
if key(sj) < key(S[k]): |
|
k = j |
|
f[j-k] = -1 |
|
else: |
|
f[j-k] = i+1 |
|
return k |
|
|
|
|
|
def multiset_combinations(m, n, g=None): |
|
""" |
|
Return the unique combinations of size ``n`` from multiset ``m``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import multiset_combinations |
|
>>> from itertools import combinations |
|
>>> [''.join(i) for i in multiset_combinations('baby', 3)] |
|
['abb', 'aby', 'bby'] |
|
|
|
>>> def count(f, s): return len(list(f(s, 3))) |
|
|
|
The number of combinations depends on the number of letters; the |
|
number of unique combinations depends on how the letters are |
|
repeated. |
|
|
|
>>> s1 = 'abracadabra' |
|
>>> s2 = 'banana tree' |
|
>>> count(combinations, s1), count(multiset_combinations, s1) |
|
(165, 23) |
|
>>> count(combinations, s2), count(multiset_combinations, s2) |
|
(165, 54) |
|
|
|
""" |
|
from sympy.core.sorting import ordered |
|
if g is None: |
|
if isinstance(m, dict): |
|
if any(as_int(v) < 0 for v in m.values()): |
|
raise ValueError('counts cannot be negative') |
|
N = sum(m.values()) |
|
if n > N: |
|
return |
|
g = [[k, m[k]] for k in ordered(m)] |
|
else: |
|
m = list(m) |
|
N = len(m) |
|
if n > N: |
|
return |
|
try: |
|
m = multiset(m) |
|
g = [(k, m[k]) for k in ordered(m)] |
|
except TypeError: |
|
m = list(ordered(m)) |
|
g = [list(i) for i in group(m, multiple=False)] |
|
del m |
|
else: |
|
|
|
N = sum(v for k, v in g) |
|
if n > N or not n: |
|
yield [] |
|
else: |
|
for i, (k, v) in enumerate(g): |
|
if v >= n: |
|
yield [k]*n |
|
v = n - 1 |
|
for v in range(min(n, v), 0, -1): |
|
for j in multiset_combinations(None, n - v, g[i + 1:]): |
|
rv = [k]*v + j |
|
if len(rv) == n: |
|
yield rv |
|
|
|
def multiset_permutations(m, size=None, g=None): |
|
""" |
|
Return the unique permutations of multiset ``m``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import multiset_permutations |
|
>>> from sympy import factorial |
|
>>> [''.join(i) for i in multiset_permutations('aab')] |
|
['aab', 'aba', 'baa'] |
|
>>> factorial(len('banana')) |
|
720 |
|
>>> len(list(multiset_permutations('banana'))) |
|
60 |
|
""" |
|
from sympy.core.sorting import ordered |
|
if g is None: |
|
if isinstance(m, dict): |
|
if any(as_int(v) < 0 for v in m.values()): |
|
raise ValueError('counts cannot be negative') |
|
g = [[k, m[k]] for k in ordered(m)] |
|
else: |
|
m = list(ordered(m)) |
|
g = [list(i) for i in group(m, multiple=False)] |
|
del m |
|
do = [gi for gi in g if gi[1] > 0] |
|
SUM = sum(gi[1] for gi in do) |
|
if not do or size is not None and (size > SUM or size < 1): |
|
if not do and size is None or size == 0: |
|
yield [] |
|
return |
|
elif size == 1: |
|
for k, v in do: |
|
yield [k] |
|
elif len(do) == 1: |
|
k, v = do[0] |
|
v = v if size is None else (size if size <= v else 0) |
|
yield [k for i in range(v)] |
|
elif all(v == 1 for k, v in do): |
|
for p in permutations([k for k, v in do], size): |
|
yield list(p) |
|
else: |
|
size = size if size is not None else SUM |
|
for i, (k, v) in enumerate(do): |
|
do[i][1] -= 1 |
|
for j in multiset_permutations(None, size - 1, do): |
|
if j: |
|
yield [k] + j |
|
do[i][1] += 1 |
|
|
|
|
|
def _partition(seq, vector, m=None): |
|
""" |
|
Return the partition of seq as specified by the partition vector. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import _partition |
|
>>> _partition('abcde', [1, 0, 1, 2, 0]) |
|
[['b', 'e'], ['a', 'c'], ['d']] |
|
|
|
Specifying the number of bins in the partition is optional: |
|
|
|
>>> _partition('abcde', [1, 0, 1, 2, 0], 3) |
|
[['b', 'e'], ['a', 'c'], ['d']] |
|
|
|
The output of _set_partitions can be passed as follows: |
|
|
|
>>> output = (3, [1, 0, 1, 2, 0]) |
|
>>> _partition('abcde', *output) |
|
[['b', 'e'], ['a', 'c'], ['d']] |
|
|
|
See Also |
|
======== |
|
|
|
combinatorics.partitions.Partition.from_rgs |
|
|
|
""" |
|
if m is None: |
|
m = max(vector) + 1 |
|
elif isinstance(vector, int): |
|
vector, m = m, vector |
|
p = [[] for i in range(m)] |
|
for i, v in enumerate(vector): |
|
p[v].append(seq[i]) |
|
return p |
|
|
|
|
|
def _set_partitions(n): |
|
"""Cycle through all partitions of n elements, yielding the |
|
current number of partitions, ``m``, and a mutable list, ``q`` |
|
such that ``element[i]`` is in part ``q[i]`` of the partition. |
|
|
|
NOTE: ``q`` is modified in place and generally should not be changed |
|
between function calls. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import _set_partitions, _partition |
|
>>> for m, q in _set_partitions(3): |
|
... print('%s %s %s' % (m, q, _partition('abc', q, m))) |
|
1 [0, 0, 0] [['a', 'b', 'c']] |
|
2 [0, 0, 1] [['a', 'b'], ['c']] |
|
2 [0, 1, 0] [['a', 'c'], ['b']] |
|
2 [0, 1, 1] [['a'], ['b', 'c']] |
|
3 [0, 1, 2] [['a'], ['b'], ['c']] |
|
|
|
Notes |
|
===== |
|
|
|
This algorithm is similar to, and solves the same problem as, |
|
Algorithm 7.2.1.5H, from volume 4A of Knuth's The Art of Computer |
|
Programming. Knuth uses the term "restricted growth string" where |
|
this code refers to a "partition vector". In each case, the meaning is |
|
the same: the value in the ith element of the vector specifies to |
|
which part the ith set element is to be assigned. |
|
|
|
At the lowest level, this code implements an n-digit big-endian |
|
counter (stored in the array q) which is incremented (with carries) to |
|
get the next partition in the sequence. A special twist is that a |
|
digit is constrained to be at most one greater than the maximum of all |
|
the digits to the left of it. The array p maintains this maximum, so |
|
that the code can efficiently decide when a digit can be incremented |
|
in place or whether it needs to be reset to 0 and trigger a carry to |
|
the next digit. The enumeration starts with all the digits 0 (which |
|
corresponds to all the set elements being assigned to the same 0th |
|
part), and ends with 0123...n, which corresponds to each set element |
|
being assigned to a different, singleton, part. |
|
|
|
This routine was rewritten to use 0-based lists while trying to |
|
preserve the beauty and efficiency of the original algorithm. |
|
|
|
References |
|
========== |
|
|
|
.. [1] Nijenhuis, Albert and Wilf, Herbert. (1978) Combinatorial Algorithms, |
|
2nd Ed, p 91, algorithm "nexequ". Available online from |
|
https://www.math.upenn.edu/~wilf/website/CombAlgDownld.html (viewed |
|
November 17, 2012). |
|
|
|
""" |
|
p = [0]*n |
|
q = [0]*n |
|
nc = 1 |
|
yield nc, q |
|
while nc != n: |
|
m = n |
|
while 1: |
|
m -= 1 |
|
i = q[m] |
|
if p[i] != 1: |
|
break |
|
q[m] = 0 |
|
i += 1 |
|
q[m] = i |
|
m += 1 |
|
nc += m - n |
|
p[0] += n - m |
|
if i == nc: |
|
p[nc] = 0 |
|
nc += 1 |
|
p[i - 1] -= 1 |
|
p[i] += 1 |
|
yield nc, q |
|
|
|
|
|
def multiset_partitions(multiset, m=None): |
|
""" |
|
Return unique partitions of the given multiset (in list form). |
|
If ``m`` is None, all multisets will be returned, otherwise only |
|
partitions with ``m`` parts will be returned. |
|
|
|
If ``multiset`` is an integer, a range [0, 1, ..., multiset - 1] |
|
will be supplied. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import multiset_partitions |
|
>>> list(multiset_partitions([1, 2, 3, 4], 2)) |
|
[[[1, 2, 3], [4]], [[1, 2, 4], [3]], [[1, 2], [3, 4]], |
|
[[1, 3, 4], [2]], [[1, 3], [2, 4]], [[1, 4], [2, 3]], |
|
[[1], [2, 3, 4]]] |
|
>>> list(multiset_partitions([1, 2, 3, 4], 1)) |
|
[[[1, 2, 3, 4]]] |
|
|
|
Only unique partitions are returned and these will be returned in a |
|
canonical order regardless of the order of the input: |
|
|
|
>>> a = [1, 2, 2, 1] |
|
>>> ans = list(multiset_partitions(a, 2)) |
|
>>> a.sort() |
|
>>> list(multiset_partitions(a, 2)) == ans |
|
True |
|
>>> a = range(3, 1, -1) |
|
>>> (list(multiset_partitions(a)) == |
|
... list(multiset_partitions(sorted(a)))) |
|
True |
|
|
|
If m is omitted then all partitions will be returned: |
|
|
|
>>> list(multiset_partitions([1, 1, 2])) |
|
[[[1, 1, 2]], [[1, 1], [2]], [[1, 2], [1]], [[1], [1], [2]]] |
|
>>> list(multiset_partitions([1]*3)) |
|
[[[1, 1, 1]], [[1], [1, 1]], [[1], [1], [1]]] |
|
|
|
Counting |
|
======== |
|
|
|
The number of partitions of a set is given by the bell number: |
|
|
|
>>> from sympy import bell |
|
>>> len(list(multiset_partitions(5))) == bell(5) == 52 |
|
True |
|
|
|
The number of partitions of length k from a set of size n is given by the |
|
Stirling Number of the 2nd kind: |
|
|
|
>>> from sympy.functions.combinatorial.numbers import stirling |
|
>>> stirling(5, 2) == len(list(multiset_partitions(5, 2))) == 15 |
|
True |
|
|
|
These comments on counting apply to *sets*, not multisets. |
|
|
|
Notes |
|
===== |
|
|
|
When all the elements are the same in the multiset, the order |
|
of the returned partitions is determined by the ``partitions`` |
|
routine. If one is counting partitions then it is better to use |
|
the ``nT`` function. |
|
|
|
See Also |
|
======== |
|
|
|
partitions |
|
sympy.combinatorics.partitions.Partition |
|
sympy.combinatorics.partitions.IntegerPartition |
|
sympy.functions.combinatorial.numbers.nT |
|
|
|
""" |
|
|
|
|
|
if isinstance(multiset, int): |
|
n = multiset |
|
if m and m > n: |
|
return |
|
multiset = list(range(n)) |
|
if m == 1: |
|
yield [multiset[:]] |
|
return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for nc, q in _set_partitions(n): |
|
if m is None or nc == m: |
|
rv = [[] for i in range(nc)] |
|
for i in range(n): |
|
rv[q[i]].append(multiset[i]) |
|
yield rv |
|
return |
|
|
|
if len(multiset) == 1 and isinstance(multiset, str): |
|
multiset = [multiset] |
|
|
|
if not has_variety(multiset): |
|
|
|
|
|
n = len(multiset) |
|
if m and m > n: |
|
return |
|
if m == 1: |
|
yield [multiset[:]] |
|
return |
|
x = multiset[:1] |
|
for size, p in partitions(n, m, size=True): |
|
if m is None or size == m: |
|
rv = [] |
|
for k in sorted(p): |
|
rv.extend([x*k]*p[k]) |
|
yield rv |
|
else: |
|
from sympy.core.sorting import ordered |
|
multiset = list(ordered(multiset)) |
|
n = len(multiset) |
|
if m and m > n: |
|
return |
|
if m == 1: |
|
yield [multiset[:]] |
|
return |
|
|
|
|
|
|
|
|
|
elements, multiplicities = zip(*group(multiset, False)) |
|
|
|
if len(elements) < len(multiset): |
|
|
|
|
|
if m: |
|
mpt = MultisetPartitionTraverser() |
|
for state in mpt.enum_range(multiplicities, m-1, m): |
|
yield list_visitor(state, elements) |
|
else: |
|
for state in multiset_partitions_taocp(multiplicities): |
|
yield list_visitor(state, elements) |
|
else: |
|
|
|
|
|
|
|
|
|
for nc, q in _set_partitions(n): |
|
if m is None or nc == m: |
|
rv = [[] for i in range(nc)] |
|
for i in range(n): |
|
rv[q[i]].append(i) |
|
yield [[multiset[j] for j in i] for i in rv] |
|
|
|
|
|
def partitions(n, m=None, k=None, size=False): |
|
"""Generate all partitions of positive integer, n. |
|
|
|
Each partition is represented as a dictionary, mapping an integer |
|
to the number of copies of that integer in the partition. For example, |
|
the first partition of 4 returned is {4: 1}, "4: one of them". |
|
|
|
Parameters |
|
========== |
|
n : int |
|
m : int, optional |
|
limits number of parts in partition (mnemonic: m, maximum parts) |
|
k : int, optional |
|
limits the numbers that are kept in the partition (mnemonic: k, keys) |
|
size : bool, default: False |
|
If ``True``, (M, P) is returned where M is the sum of the |
|
multiplicities and P is the generated partition. |
|
If ``False``, only the generated partition is returned. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import partitions |
|
|
|
The numbers appearing in the partition (the key of the returned dict) |
|
are limited with k: |
|
|
|
>>> for p in partitions(6, k=2): # doctest: +SKIP |
|
... print(p) |
|
{2: 3} |
|
{1: 2, 2: 2} |
|
{1: 4, 2: 1} |
|
{1: 6} |
|
|
|
The maximum number of parts in the partition (the sum of the values in |
|
the returned dict) are limited with m (default value, None, gives |
|
partitions from 1 through n): |
|
|
|
>>> for p in partitions(6, m=2): # doctest: +SKIP |
|
... print(p) |
|
... |
|
{6: 1} |
|
{1: 1, 5: 1} |
|
{2: 1, 4: 1} |
|
{3: 2} |
|
|
|
References |
|
========== |
|
|
|
.. [1] modified from Tim Peter's version to allow for k and m values: |
|
https://code.activestate.com/recipes/218332-generator-for-integer-partitions/ |
|
|
|
See Also |
|
======== |
|
|
|
sympy.combinatorics.partitions.Partition |
|
sympy.combinatorics.partitions.IntegerPartition |
|
|
|
""" |
|
if (n <= 0 or |
|
m is not None and m < 1 or |
|
k is not None and k < 1 or |
|
m and k and m*k < n): |
|
|
|
|
|
|
|
if size: |
|
yield 0, {} |
|
else: |
|
yield {} |
|
return |
|
|
|
if m is None: |
|
m = n |
|
else: |
|
m = min(m, n) |
|
k = min(k or n, n) |
|
|
|
n, m, k = as_int(n), as_int(m), as_int(k) |
|
q, r = divmod(n, k) |
|
ms = {k: q} |
|
keys = [k] |
|
if r: |
|
ms[r] = 1 |
|
keys.append(r) |
|
room = m - q - bool(r) |
|
if size: |
|
yield sum(ms.values()), ms.copy() |
|
else: |
|
yield ms.copy() |
|
|
|
while keys != [1]: |
|
|
|
if keys[-1] == 1: |
|
del keys[-1] |
|
reuse = ms.pop(1) |
|
room += reuse |
|
else: |
|
reuse = 0 |
|
|
|
while 1: |
|
|
|
|
|
i = keys[-1] |
|
newcount = ms[i] = ms[i] - 1 |
|
reuse += i |
|
if newcount == 0: |
|
del keys[-1], ms[i] |
|
room += 1 |
|
|
|
|
|
i -= 1 |
|
q, r = divmod(reuse, i) |
|
need = q + bool(r) |
|
if need > room: |
|
if not keys: |
|
return |
|
continue |
|
|
|
ms[i] = q |
|
keys.append(i) |
|
if r: |
|
ms[r] = 1 |
|
keys.append(r) |
|
break |
|
room -= need |
|
if size: |
|
yield sum(ms.values()), ms.copy() |
|
else: |
|
yield ms.copy() |
|
|
|
|
|
def ordered_partitions(n, m=None, sort=True): |
|
"""Generates ordered partitions of integer *n*. |
|
|
|
Parameters |
|
========== |
|
n : int |
|
m : int, optional |
|
The default value gives partitions of all sizes else only |
|
those with size m. In addition, if *m* is not None then |
|
partitions are generated *in place* (see examples). |
|
sort : bool, default: True |
|
Controls whether partitions are |
|
returned in sorted order when *m* is not None; when False, |
|
the partitions are returned as fast as possible with elements |
|
sorted, but when m|n the partitions will not be in |
|
ascending lexicographical order. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import ordered_partitions |
|
|
|
All partitions of 5 in ascending lexicographical: |
|
|
|
>>> for p in ordered_partitions(5): |
|
... print(p) |
|
[1, 1, 1, 1, 1] |
|
[1, 1, 1, 2] |
|
[1, 1, 3] |
|
[1, 2, 2] |
|
[1, 4] |
|
[2, 3] |
|
[5] |
|
|
|
Only partitions of 5 with two parts: |
|
|
|
>>> for p in ordered_partitions(5, 2): |
|
... print(p) |
|
[1, 4] |
|
[2, 3] |
|
|
|
When ``m`` is given, a given list objects will be used more than |
|
once for speed reasons so you will not see the correct partitions |
|
unless you make a copy of each as it is generated: |
|
|
|
>>> [p for p in ordered_partitions(7, 3)] |
|
[[1, 1, 1], [1, 1, 1], [1, 1, 1], [2, 2, 2]] |
|
>>> [list(p) for p in ordered_partitions(7, 3)] |
|
[[1, 1, 5], [1, 2, 4], [1, 3, 3], [2, 2, 3]] |
|
|
|
When ``n`` is a multiple of ``m``, the elements are still sorted |
|
but the partitions themselves will be *unordered* if sort is False; |
|
the default is to return them in ascending lexicographical order. |
|
|
|
>>> for p in ordered_partitions(6, 2): |
|
... print(p) |
|
[1, 5] |
|
[2, 4] |
|
[3, 3] |
|
|
|
But if speed is more important than ordering, sort can be set to |
|
False: |
|
|
|
>>> for p in ordered_partitions(6, 2, sort=False): |
|
... print(p) |
|
[1, 5] |
|
[3, 3] |
|
[2, 4] |
|
|
|
References |
|
========== |
|
|
|
.. [1] Generating Integer Partitions, [online], |
|
Available: https://jeromekelleher.net/generating-integer-partitions.html |
|
.. [2] Jerome Kelleher and Barry O'Sullivan, "Generating All |
|
Partitions: A Comparison Of Two Encodings", [online], |
|
Available: https://arxiv.org/pdf/0909.2331v2.pdf |
|
""" |
|
if n < 1 or m is not None and m < 1: |
|
|
|
|
|
|
|
yield [] |
|
return |
|
|
|
if m is None: |
|
|
|
|
|
|
|
|
|
a = [1]*n |
|
y = -1 |
|
v = n |
|
while v > 0: |
|
v -= 1 |
|
x = a[v] + 1 |
|
while y >= 2 * x: |
|
a[v] = x |
|
y -= x |
|
v += 1 |
|
w = v + 1 |
|
while x <= y: |
|
a[v] = x |
|
a[w] = y |
|
yield a[:w + 1] |
|
x += 1 |
|
y -= 1 |
|
a[v] = x + y |
|
y = a[v] - 1 |
|
yield a[:w] |
|
elif m == 1: |
|
yield [n] |
|
elif n == m: |
|
yield [1]*n |
|
else: |
|
|
|
for b in range(1, n//m + 1): |
|
a = [b]*m |
|
x = n - b*m |
|
if not x: |
|
if sort: |
|
yield a |
|
elif not sort and x <= m: |
|
for ax in ordered_partitions(x, sort=False): |
|
mi = len(ax) |
|
a[-mi:] = [i + b for i in ax] |
|
yield a |
|
a[-mi:] = [b]*mi |
|
else: |
|
for mi in range(1, m): |
|
for ax in ordered_partitions(x, mi, sort=True): |
|
a[-mi:] = [i + b for i in ax] |
|
yield a |
|
a[-mi:] = [b]*mi |
|
|
|
|
|
def binary_partitions(n): |
|
""" |
|
Generates the binary partition of *n*. |
|
|
|
A binary partition consists only of numbers that are |
|
powers of two. Each step reduces a `2^{k+1}` to `2^k` and |
|
`2^k`. Thus 16 is converted to 8 and 8. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import binary_partitions |
|
>>> for i in binary_partitions(5): |
|
... print(i) |
|
... |
|
[4, 1] |
|
[2, 2, 1] |
|
[2, 1, 1, 1] |
|
[1, 1, 1, 1, 1] |
|
|
|
References |
|
========== |
|
|
|
.. [1] TAOCP 4, section 7.2.1.5, problem 64 |
|
|
|
""" |
|
from math import ceil, log2 |
|
power = int(2**(ceil(log2(n)))) |
|
acc = 0 |
|
partition = [] |
|
while power: |
|
if acc + power <= n: |
|
partition.append(power) |
|
acc += power |
|
power >>= 1 |
|
|
|
last_num = len(partition) - 1 - (n & 1) |
|
while last_num >= 0: |
|
yield partition |
|
if partition[last_num] == 2: |
|
partition[last_num] = 1 |
|
partition.append(1) |
|
last_num -= 1 |
|
continue |
|
partition.append(1) |
|
partition[last_num] >>= 1 |
|
x = partition[last_num + 1] = partition[last_num] |
|
last_num += 1 |
|
while x > 1: |
|
if x <= len(partition) - last_num - 1: |
|
del partition[-x + 1:] |
|
last_num += 1 |
|
partition[last_num] = x |
|
else: |
|
x >>= 1 |
|
yield [1]*n |
|
|
|
|
|
def has_dups(seq): |
|
"""Return True if there are any duplicate elements in ``seq``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import has_dups, Dict, Set |
|
>>> has_dups((1, 2, 1)) |
|
True |
|
>>> has_dups(range(3)) |
|
False |
|
>>> all(has_dups(c) is False for c in (set(), Set(), dict(), Dict())) |
|
True |
|
""" |
|
from sympy.core.containers import Dict |
|
from sympy.sets.sets import Set |
|
if isinstance(seq, (dict, set, Dict, Set)): |
|
return False |
|
unique = set() |
|
try: |
|
return any(True for s in seq if s in unique or unique.add(s)) |
|
except TypeError: |
|
return len(seq) != len(list(uniq(seq))) |
|
|
|
|
|
def has_variety(seq): |
|
"""Return True if there are any different elements in ``seq``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import has_variety |
|
|
|
>>> has_variety((1, 2, 1)) |
|
True |
|
>>> has_variety((1, 1, 1)) |
|
False |
|
""" |
|
for i, s in enumerate(seq): |
|
if i == 0: |
|
sentinel = s |
|
else: |
|
if s != sentinel: |
|
return True |
|
return False |
|
|
|
|
|
def uniq(seq, result=None): |
|
""" |
|
Yield unique elements from ``seq`` as an iterator. The second |
|
parameter ``result`` is used internally; it is not necessary |
|
to pass anything for this. |
|
|
|
Note: changing the sequence during iteration will raise a |
|
RuntimeError if the size of the sequence is known; if you pass |
|
an iterator and advance the iterator you will change the |
|
output of this routine but there will be no warning. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import uniq |
|
>>> dat = [1, 4, 1, 5, 4, 2, 1, 2] |
|
>>> type(uniq(dat)) in (list, tuple) |
|
False |
|
|
|
>>> list(uniq(dat)) |
|
[1, 4, 5, 2] |
|
>>> list(uniq(x for x in dat)) |
|
[1, 4, 5, 2] |
|
>>> list(uniq([[1], [2, 1], [1]])) |
|
[[1], [2, 1]] |
|
""" |
|
try: |
|
n = len(seq) |
|
except TypeError: |
|
n = None |
|
def check(): |
|
|
|
|
|
|
|
if n is not None and len(seq) != n: |
|
raise RuntimeError('sequence changed size during iteration') |
|
try: |
|
seen = set() |
|
result = result or [] |
|
for i, s in enumerate(seq): |
|
if not (s in seen or seen.add(s)): |
|
yield s |
|
check() |
|
except TypeError: |
|
if s not in result: |
|
yield s |
|
check() |
|
result.append(s) |
|
if hasattr(seq, '__getitem__'): |
|
yield from uniq(seq[i + 1:], result) |
|
else: |
|
yield from uniq(seq, result) |
|
|
|
|
|
def generate_bell(n): |
|
"""Return permutations of [0, 1, ..., n - 1] such that each permutation |
|
differs from the last by the exchange of a single pair of neighbors. |
|
The ``n!`` permutations are returned as an iterator. In order to obtain |
|
the next permutation from a random starting permutation, use the |
|
``next_trotterjohnson`` method of the Permutation class (which generates |
|
the same sequence in a different manner). |
|
|
|
Examples |
|
======== |
|
|
|
>>> from itertools import permutations |
|
>>> from sympy.utilities.iterables import generate_bell |
|
>>> from sympy import zeros, Matrix |
|
|
|
This is the sort of permutation used in the ringing of physical bells, |
|
and does not produce permutations in lexicographical order. Rather, the |
|
permutations differ from each other by exactly one inversion, and the |
|
position at which the swapping occurs varies periodically in a simple |
|
fashion. Consider the first few permutations of 4 elements generated |
|
by ``permutations`` and ``generate_bell``: |
|
|
|
>>> list(permutations(range(4)))[:5] |
|
[(0, 1, 2, 3), (0, 1, 3, 2), (0, 2, 1, 3), (0, 2, 3, 1), (0, 3, 1, 2)] |
|
>>> list(generate_bell(4))[:5] |
|
[(0, 1, 2, 3), (0, 1, 3, 2), (0, 3, 1, 2), (3, 0, 1, 2), (3, 0, 2, 1)] |
|
|
|
Notice how the 2nd and 3rd lexicographical permutations have 3 elements |
|
out of place whereas each "bell" permutation always has only two |
|
elements out of place relative to the previous permutation (and so the |
|
signature (+/-1) of a permutation is opposite of the signature of the |
|
previous permutation). |
|
|
|
How the position of inversion varies across the elements can be seen |
|
by tracing out where the largest number appears in the permutations: |
|
|
|
>>> m = zeros(4, 24) |
|
>>> for i, p in enumerate(generate_bell(4)): |
|
... m[:, i] = Matrix([j - 3 for j in list(p)]) # make largest zero |
|
>>> m.print_nonzero('X') |
|
[XXX XXXXXX XXXXXX XXX] |
|
[XX XX XXXX XX XXXX XX XX] |
|
[X XXXX XX XXXX XX XXXX X] |
|
[ XXXXXX XXXXXX XXXXXX ] |
|
|
|
See Also |
|
======== |
|
|
|
sympy.combinatorics.permutations.Permutation.next_trotterjohnson |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://en.wikipedia.org/wiki/Method_ringing |
|
|
|
.. [2] https://stackoverflow.com/questions/4856615/recursive-permutation/4857018 |
|
|
|
.. [3] https://web.archive.org/web/20160313023044/http://programminggeeks.com/bell-algorithm-for-permutation/ |
|
|
|
.. [4] https://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm |
|
|
|
.. [5] Generating involutions, derangements, and relatives by ECO |
|
Vincent Vajnovszki, DMTCS vol 1 issue 12, 2010 |
|
|
|
""" |
|
n = as_int(n) |
|
if n < 1: |
|
raise ValueError('n must be a positive integer') |
|
if n == 1: |
|
yield (0,) |
|
elif n == 2: |
|
yield (0, 1) |
|
yield (1, 0) |
|
elif n == 3: |
|
yield from [(0, 1, 2), (0, 2, 1), (2, 0, 1), (2, 1, 0), (1, 2, 0), (1, 0, 2)] |
|
else: |
|
m = n - 1 |
|
op = [0] + [-1]*m |
|
l = list(range(n)) |
|
while True: |
|
yield tuple(l) |
|
|
|
big = None, -1 |
|
for i in range(n): |
|
if op[i] and l[i] > big[1]: |
|
big = i, l[i] |
|
i, _ = big |
|
if i is None: |
|
break |
|
|
|
j = i + op[i] |
|
l[i], l[j] = l[j], l[i] |
|
op[i], op[j] = op[j], op[i] |
|
|
|
|
|
if j == 0 or j == m or l[j + op[j]] > l[j]: |
|
op[j] = 0 |
|
|
|
for i in range(j): |
|
if l[i] > l[j]: |
|
op[i] = 1 |
|
|
|
for i in range(j + 1, n): |
|
if l[i] > l[j]: |
|
op[i] = -1 |
|
|
|
|
|
def generate_involutions(n): |
|
""" |
|
Generates involutions. |
|
|
|
An involution is a permutation that when multiplied |
|
by itself equals the identity permutation. In this |
|
implementation the involutions are generated using |
|
Fixed Points. |
|
|
|
Alternatively, an involution can be considered as |
|
a permutation that does not contain any cycles with |
|
a length that is greater than two. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import generate_involutions |
|
>>> list(generate_involutions(3)) |
|
[(0, 1, 2), (0, 2, 1), (1, 0, 2), (2, 1, 0)] |
|
>>> len(list(generate_involutions(4))) |
|
10 |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://mathworld.wolfram.com/PermutationInvolution.html |
|
|
|
""" |
|
idx = list(range(n)) |
|
for p in permutations(idx): |
|
for i in idx: |
|
if p[p[i]] != i: |
|
break |
|
else: |
|
yield p |
|
|
|
|
|
def multiset_derangements(s): |
|
"""Generate derangements of the elements of s *in place*. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import multiset_derangements, uniq |
|
|
|
Because the derangements of multisets (not sets) are generated |
|
in place, copies of the return value must be made if a collection |
|
of derangements is desired or else all values will be the same: |
|
|
|
>>> list(uniq([i for i in multiset_derangements('1233')])) |
|
[[None, None, None, None]] |
|
>>> [i.copy() for i in multiset_derangements('1233')] |
|
[['3', '3', '1', '2'], ['3', '3', '2', '1']] |
|
>>> [''.join(i) for i in multiset_derangements('1233')] |
|
['3312', '3321'] |
|
""" |
|
from sympy.core.sorting import ordered |
|
|
|
|
|
try: |
|
ms = multiset(s) |
|
except TypeError: |
|
|
|
key = dict(enumerate(ordered(uniq(s)))) |
|
h = [] |
|
for si in s: |
|
for k in key: |
|
if key[k] == si: |
|
h.append(k) |
|
break |
|
for i in multiset_derangements(h): |
|
yield [key[j] for j in i] |
|
return |
|
|
|
mx = max(ms.values()) |
|
n = len(s) |
|
|
|
|
|
|
|
|
|
|
|
if mx*2 > n: |
|
return |
|
|
|
|
|
if len(ms) == n: |
|
yield from _set_derangements(s) |
|
return |
|
|
|
|
|
|
|
|
|
|
|
for M in ms: |
|
if ms[M] == mx: |
|
break |
|
|
|
inonM = [i for i in range(n) if s[i] != M] |
|
iM = [i for i in range(n) if s[i] == M] |
|
rv = [None]*n |
|
|
|
|
|
if 2*mx == n: |
|
|
|
for i in inonM: |
|
rv[i] = M |
|
|
|
for p in multiset_permutations([s[i] for i in inonM]): |
|
for i, pi in zip(iM, p): |
|
rv[i] = pi |
|
yield rv |
|
|
|
rv[:] = [None]*n |
|
return |
|
|
|
|
|
|
|
|
|
|
|
if n - 2*mx == 1 and len(ms.values()) == n - mx + 1: |
|
for i, i1 in enumerate(inonM): |
|
ifill = inonM[:i] + inonM[i+1:] |
|
for j in ifill: |
|
rv[j] = M |
|
for p in permutations([s[j] for j in ifill]): |
|
rv[i1] = s[i1] |
|
for j, pi in zip(iM, p): |
|
rv[j] = pi |
|
k = i1 |
|
for j in iM: |
|
rv[j], rv[k] = rv[k], rv[j] |
|
yield rv |
|
k = j |
|
|
|
rv[:] = [None]*n |
|
return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def finish_derangements(): |
|
"""Place the last two elements into the partially completed |
|
derangement, and yield the results. |
|
""" |
|
|
|
a = take[1][0] |
|
a_ct = take[1][1] |
|
b = take[0][0] |
|
b_ct = take[0][1] |
|
|
|
|
|
|
|
forced_a = [] |
|
forced_b = [] |
|
open_free = [] |
|
for i in range(len(s)): |
|
if rv[i] is None: |
|
if s[i] == a: |
|
forced_b.append(i) |
|
elif s[i] == b: |
|
forced_a.append(i) |
|
else: |
|
open_free.append(i) |
|
|
|
if len(forced_a) > a_ct or len(forced_b) > b_ct: |
|
|
|
return |
|
|
|
for i in forced_a: |
|
rv[i] = a |
|
for i in forced_b: |
|
rv[i] = b |
|
for a_place in combinations(open_free, a_ct - len(forced_a)): |
|
for a_pos in a_place: |
|
rv[a_pos] = a |
|
for i in open_free: |
|
if rv[i] is None: |
|
rv[i] = b |
|
yield rv |
|
|
|
for i in open_free: |
|
rv[i] = None |
|
|
|
|
|
for i in forced_a: |
|
rv[i] = None |
|
for i in forced_b: |
|
rv[i] = None |
|
|
|
def iopen(v): |
|
|
|
|
|
|
|
return [i for i in range(n) if rv[i] is None and s[i] != v] |
|
|
|
def do(j): |
|
if j == 1: |
|
|
|
|
|
yield from finish_derangements() |
|
else: |
|
|
|
|
|
M, mx = take[j] |
|
for i in combinations(iopen(M), mx): |
|
|
|
for ii in i: |
|
rv[ii] = M |
|
|
|
yield from do(j - 1) |
|
|
|
|
|
for ii in i: |
|
rv[ii] = None |
|
|
|
|
|
take = sorted(ms.items(), key=lambda x:(x[1], x[0])) |
|
yield from do(len(take) - 1) |
|
rv[:] = [None]*n |
|
|
|
|
|
def random_derangement(t, choice=None, strict=True): |
|
"""Return a list of elements in which none are in the same positions |
|
as they were originally. If an element fills more than half of the positions |
|
then an error will be raised since no derangement is possible. To obtain |
|
a derangement of as many items as possible--with some of the most numerous |
|
remaining in their original positions--pass `strict=False`. To produce a |
|
pseudorandom derangment, pass a pseudorandom selector like `choice` (see |
|
below). |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import random_derangement |
|
>>> t = 'SymPy: a CAS in pure Python' |
|
>>> d = random_derangement(t) |
|
>>> all(i != j for i, j in zip(d, t)) |
|
True |
|
|
|
A predictable result can be obtained by using a pseudorandom |
|
generator for the choice: |
|
|
|
>>> from sympy.core.random import seed, choice as c |
|
>>> seed(1) |
|
>>> d = [''.join(random_derangement(t, c)) for i in range(5)] |
|
>>> assert len(set(d)) != 1 # we got different values |
|
|
|
By reseeding, the same sequence can be obtained: |
|
|
|
>>> seed(1) |
|
>>> d2 = [''.join(random_derangement(t, c)) for i in range(5)] |
|
>>> assert d == d2 |
|
""" |
|
if choice is None: |
|
import secrets |
|
choice = secrets.choice |
|
def shuffle(rv): |
|
'''Knuth shuffle''' |
|
for i in range(len(rv) - 1, 0, -1): |
|
x = choice(rv[:i + 1]) |
|
j = rv.index(x) |
|
rv[i], rv[j] = rv[j], rv[i] |
|
def pick(rv, n): |
|
'''shuffle rv and return the first n values |
|
''' |
|
shuffle(rv) |
|
return rv[:n] |
|
ms = multiset(t) |
|
tot = len(t) |
|
ms = sorted(ms.items(), key=lambda x: x[1]) |
|
|
|
|
|
|
|
M, mx = ms[-1] |
|
n = len(t) |
|
xs = 2*mx - tot |
|
if xs > 0: |
|
if strict: |
|
raise ValueError('no derangement possible') |
|
opts = [i for (i, c) in enumerate(t) if c == ms[-1][0]] |
|
pick(opts, xs) |
|
stay = sorted(opts[:xs]) |
|
rv = list(t) |
|
for i in reversed(stay): |
|
rv.pop(i) |
|
rv = random_derangement(rv, choice) |
|
for i in stay: |
|
rv.insert(i, ms[-1][0]) |
|
return ''.join(rv) if type(t) is str else rv |
|
|
|
if n == len(ms): |
|
|
|
rv = list(t) |
|
while True: |
|
shuffle(rv) |
|
if all(i != j for i,j in zip(rv, t)): |
|
break |
|
else: |
|
|
|
rv = [None]*n |
|
while True: |
|
j = 0 |
|
while j > -len(ms): |
|
j -= 1 |
|
e, c = ms[j] |
|
opts = [i for i in range(n) if rv[i] is None and t[i] != e] |
|
if len(opts) < c: |
|
for i in range(n): |
|
rv[i] = None |
|
break |
|
pick(opts, c) |
|
for i in range(c): |
|
rv[opts[i]] = e |
|
else: |
|
return rv |
|
return rv |
|
|
|
|
|
def _set_derangements(s): |
|
""" |
|
yield derangements of items in ``s`` which are assumed to contain |
|
no repeated elements |
|
""" |
|
if len(s) < 2: |
|
return |
|
if len(s) == 2: |
|
yield [s[1], s[0]] |
|
return |
|
if len(s) == 3: |
|
yield [s[1], s[2], s[0]] |
|
yield [s[2], s[0], s[1]] |
|
return |
|
for p in permutations(s): |
|
if not any(i == j for i, j in zip(p, s)): |
|
yield list(p) |
|
|
|
|
|
def generate_derangements(s): |
|
""" |
|
Return unique derangements of the elements of iterable ``s``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import generate_derangements |
|
>>> list(generate_derangements([0, 1, 2])) |
|
[[1, 2, 0], [2, 0, 1]] |
|
>>> list(generate_derangements([0, 1, 2, 2])) |
|
[[2, 2, 0, 1], [2, 2, 1, 0]] |
|
>>> list(generate_derangements([0, 1, 1])) |
|
[] |
|
|
|
See Also |
|
======== |
|
|
|
sympy.functions.combinatorial.factorials.subfactorial |
|
|
|
""" |
|
if not has_dups(s): |
|
yield from _set_derangements(s) |
|
else: |
|
for p in multiset_derangements(s): |
|
yield list(p) |
|
|
|
|
|
def necklaces(n, k, free=False): |
|
""" |
|
A routine to generate necklaces that may (free=True) or may not |
|
(free=False) be turned over to be viewed. The "necklaces" returned |
|
are comprised of ``n`` integers (beads) with ``k`` different |
|
values (colors). Only unique necklaces are returned. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import necklaces, bracelets |
|
>>> def show(s, i): |
|
... return ''.join(s[j] for j in i) |
|
|
|
The "unrestricted necklace" is sometimes also referred to as a |
|
"bracelet" (an object that can be turned over, a sequence that can |
|
be reversed) and the term "necklace" is used to imply a sequence |
|
that cannot be reversed. So ACB == ABC for a bracelet (rotate and |
|
reverse) while the two are different for a necklace since rotation |
|
alone cannot make the two sequences the same. |
|
|
|
(mnemonic: Bracelets can be viewed Backwards, but Not Necklaces.) |
|
|
|
>>> B = [show('ABC', i) for i in bracelets(3, 3)] |
|
>>> N = [show('ABC', i) for i in necklaces(3, 3)] |
|
>>> set(N) - set(B) |
|
{'ACB'} |
|
|
|
>>> list(necklaces(4, 2)) |
|
[(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 1, 1), |
|
(0, 1, 0, 1), (0, 1, 1, 1), (1, 1, 1, 1)] |
|
|
|
>>> [show('.o', i) for i in bracelets(4, 2)] |
|
['....', '...o', '..oo', '.o.o', '.ooo', 'oooo'] |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://mathworld.wolfram.com/Necklace.html |
|
|
|
.. [2] Frank Ruskey, Carla Savage, and Terry Min Yih Wang, |
|
Generating necklaces, Journal of Algorithms 13 (1992), 414-430; |
|
https://doi.org/10.1016/0196-6774(92)90047-G |
|
|
|
""" |
|
|
|
if k == 0 and n > 0: |
|
return |
|
a = [0]*n |
|
yield tuple(a) |
|
if n == 0: |
|
return |
|
while True: |
|
i = n - 1 |
|
while a[i] == k - 1: |
|
i -= 1 |
|
if i == -1: |
|
return |
|
a[i] += 1 |
|
for j in range(n - i - 1): |
|
a[j + i + 1] = a[j] |
|
if n % (i + 1) == 0 and (not free or all(a <= a[j::-1] + a[-1:j:-1] for j in range(n - 1))): |
|
|
|
yield tuple(a) |
|
|
|
|
|
def bracelets(n, k): |
|
"""Wrapper to necklaces to return a free (unrestricted) necklace.""" |
|
return necklaces(n, k, free=True) |
|
|
|
|
|
def generate_oriented_forest(n): |
|
""" |
|
This algorithm generates oriented forests. |
|
|
|
An oriented graph is a directed graph having no symmetric pair of directed |
|
edges. A forest is an acyclic graph, i.e., it has no cycles. A forest can |
|
also be described as a disjoint union of trees, which are graphs in which |
|
any two vertices are connected by exactly one simple path. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import generate_oriented_forest |
|
>>> list(generate_oriented_forest(4)) |
|
[[0, 1, 2, 3], [0, 1, 2, 2], [0, 1, 2, 1], [0, 1, 2, 0], \ |
|
[0, 1, 1, 1], [0, 1, 1, 0], [0, 1, 0, 1], [0, 1, 0, 0], [0, 0, 0, 0]] |
|
|
|
References |
|
========== |
|
|
|
.. [1] T. Beyer and S.M. Hedetniemi: constant time generation of |
|
rooted trees, SIAM J. Computing Vol. 9, No. 4, November 1980 |
|
|
|
.. [2] https://stackoverflow.com/questions/1633833/oriented-forest-taocp-algorithm-in-python |
|
|
|
""" |
|
P = list(range(-1, n)) |
|
while True: |
|
yield P[1:] |
|
if P[n] > 0: |
|
P[n] = P[P[n]] |
|
else: |
|
for p in range(n - 1, 0, -1): |
|
if P[p] != 0: |
|
target = P[p] - 1 |
|
for q in range(p - 1, 0, -1): |
|
if P[q] == target: |
|
break |
|
offset = p - q |
|
for i in range(p, n + 1): |
|
P[i] = P[i - offset] |
|
break |
|
else: |
|
break |
|
|
|
|
|
def minlex(seq, directed=True, key=None): |
|
r""" |
|
Return the rotation of the sequence in which the lexically smallest |
|
elements appear first, e.g. `cba \rightarrow acb`. |
|
|
|
The sequence returned is a tuple, unless the input sequence is a string |
|
in which case a string is returned. |
|
|
|
If ``directed`` is False then the smaller of the sequence and the |
|
reversed sequence is returned, e.g. `cba \rightarrow abc`. |
|
|
|
If ``key`` is not None then it is used to extract a comparison key from each element in iterable. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.combinatorics.polyhedron import minlex |
|
>>> minlex((1, 2, 0)) |
|
(0, 1, 2) |
|
>>> minlex((1, 0, 2)) |
|
(0, 2, 1) |
|
>>> minlex((1, 0, 2), directed=False) |
|
(0, 1, 2) |
|
|
|
>>> minlex('11010011000', directed=True) |
|
'00011010011' |
|
>>> minlex('11010011000', directed=False) |
|
'00011001011' |
|
|
|
>>> minlex(('bb', 'aaa', 'c', 'a')) |
|
('a', 'bb', 'aaa', 'c') |
|
>>> minlex(('bb', 'aaa', 'c', 'a'), key=len) |
|
('c', 'a', 'bb', 'aaa') |
|
|
|
""" |
|
from sympy.functions.elementary.miscellaneous import Id |
|
if key is None: key = Id |
|
best = rotate_left(seq, least_rotation(seq, key=key)) |
|
if not directed: |
|
rseq = seq[::-1] |
|
rbest = rotate_left(rseq, least_rotation(rseq, key=key)) |
|
best = min(best, rbest, key=key) |
|
|
|
|
|
return tuple(best) if not isinstance(seq, str) else best |
|
|
|
|
|
def runs(seq, op=gt): |
|
"""Group the sequence into lists in which successive elements |
|
all compare the same with the comparison operator, ``op``: |
|
op(seq[i + 1], seq[i]) is True from all elements in a run. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import runs |
|
>>> from operator import ge |
|
>>> runs([0, 1, 2, 2, 1, 4, 3, 2, 2]) |
|
[[0, 1, 2], [2], [1, 4], [3], [2], [2]] |
|
>>> runs([0, 1, 2, 2, 1, 4, 3, 2, 2], op=ge) |
|
[[0, 1, 2, 2], [1, 4], [3], [2, 2]] |
|
""" |
|
cycles = [] |
|
seq = iter(seq) |
|
try: |
|
run = [next(seq)] |
|
except StopIteration: |
|
return [] |
|
while True: |
|
try: |
|
ei = next(seq) |
|
except StopIteration: |
|
break |
|
if op(ei, run[-1]): |
|
run.append(ei) |
|
continue |
|
else: |
|
cycles.append(run) |
|
run = [ei] |
|
if run: |
|
cycles.append(run) |
|
return cycles |
|
|
|
|
|
def sequence_partitions(l, n, /): |
|
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 : Sequence[T] |
|
A nonempty sequence of any Python objects |
|
|
|
n : int |
|
A positive integer |
|
|
|
Yields |
|
====== |
|
|
|
out : list[Sequence[T]] |
|
A list of sequences with concatenation equals $l$. |
|
This should conform with the type of $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 |
|
""" |
|
|
|
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, n, /): |
|
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_partitions` because |
|
empty sequence can fill everywhere, so we try to provide different |
|
utility for this. |
|
|
|
Parameters |
|
========== |
|
|
|
l : Sequence[T] |
|
A sequence of any Python objects (can be possibly empty) |
|
|
|
n : int |
|
A positive integer |
|
|
|
Yields |
|
====== |
|
|
|
out : list[Sequence[T]] |
|
A list of sequences with concatenation equals $l$. |
|
This should conform with the type of $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. |
|
|
|
Examples |
|
======== |
|
|
|
The default is to give the items in the same order, but grouped |
|
into k partitions without any reordering: |
|
|
|
>>> from sympy.utilities.iterables import kbins |
|
>>> for p in kbins(list(range(5)), 2): |
|
... print(p) |
|
... |
|
[[0], [1, 2, 3, 4]] |
|
[[0, 1], [2, 3, 4]] |
|
[[0, 1, 2], [3, 4]] |
|
[[0, 1, 2, 3], [4]] |
|
|
|
The ``ordered`` flag is either None (to give the simple partition |
|
of the elements) or is a 2 digit integer indicating whether the order of |
|
the bins and the order of the items in the bins matters. Given:: |
|
|
|
A = [[0], [1, 2]] |
|
B = [[1, 2], [0]] |
|
C = [[2, 1], [0]] |
|
D = [[0], [2, 1]] |
|
|
|
the following values for ``ordered`` have the shown meanings:: |
|
|
|
00 means A == B == C == D |
|
01 means A == B |
|
10 means A == D |
|
11 means A == A |
|
|
|
>>> for ordered_flag in [None, 0, 1, 10, 11]: |
|
... print('ordered = %s' % ordered_flag) |
|
... for p in kbins(list(range(3)), 2, ordered=ordered_flag): |
|
... print(' %s' % p) |
|
... |
|
ordered = None |
|
[[0], [1, 2]] |
|
[[0, 1], [2]] |
|
ordered = 0 |
|
[[0, 1], [2]] |
|
[[0, 2], [1]] |
|
[[0], [1, 2]] |
|
ordered = 1 |
|
[[0], [1, 2]] |
|
[[0], [2, 1]] |
|
[[1], [0, 2]] |
|
[[1], [2, 0]] |
|
[[2], [0, 1]] |
|
[[2], [1, 0]] |
|
ordered = 10 |
|
[[0, 1], [2]] |
|
[[2], [0, 1]] |
|
[[0, 2], [1]] |
|
[[1], [0, 2]] |
|
[[0], [1, 2]] |
|
[[1, 2], [0]] |
|
ordered = 11 |
|
[[0], [1, 2]] |
|
[[0, 1], [2]] |
|
[[0], [2, 1]] |
|
[[0, 2], [1]] |
|
[[1], [0, 2]] |
|
[[1, 0], [2]] |
|
[[1], [2, 0]] |
|
[[1, 2], [0]] |
|
[[2], [0, 1]] |
|
[[2, 0], [1]] |
|
[[2], [1, 0]] |
|
[[2, 1], [0]] |
|
|
|
See Also |
|
======== |
|
|
|
partitions, multiset_partitions |
|
|
|
""" |
|
if ordered is None: |
|
yield from sequence_partitions(l, k) |
|
elif ordered == 11: |
|
for pl in multiset_permutations(l): |
|
pl = list(pl) |
|
yield from sequence_partitions(pl, k) |
|
elif ordered == 00: |
|
yield from multiset_partitions(l, k) |
|
elif ordered == 10: |
|
for p in multiset_partitions(l, k): |
|
for perm in permutations(p): |
|
yield list(perm) |
|
elif ordered == 1: |
|
for kgot, p in partitions(len(l), k, size=True): |
|
if kgot != k: |
|
continue |
|
for li in multiset_permutations(l): |
|
rv = [] |
|
i = j = 0 |
|
li = list(li) |
|
for size, multiplicity in sorted(p.items()): |
|
for m in range(multiplicity): |
|
j = i + size |
|
rv.append(li[i: j]) |
|
i = j |
|
yield rv |
|
else: |
|
raise ValueError( |
|
'ordered must be one of 00, 01, 10 or 11, not %s' % ordered) |
|
|
|
|
|
def permute_signs(t): |
|
"""Return iterator in which the signs of non-zero elements |
|
of t are permuted. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import permute_signs |
|
>>> list(permute_signs((0, 1, 2))) |
|
[(0, 1, 2), (0, -1, 2), (0, 1, -2), (0, -1, -2)] |
|
""" |
|
for signs in product(*[(1, -1)]*(len(t) - t.count(0))): |
|
signs = list(signs) |
|
yield type(t)([i*signs.pop() if i else i for i in t]) |
|
|
|
|
|
def signed_permutations(t): |
|
"""Return iterator in which the signs of non-zero elements |
|
of t and the order of the elements are permuted and all |
|
returned values are unique. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import signed_permutations |
|
>>> list(signed_permutations((0, 1, 2))) |
|
[(0, 1, 2), (0, -1, 2), (0, 1, -2), (0, -1, -2), (0, 2, 1), |
|
(0, -2, 1), (0, 2, -1), (0, -2, -1), (1, 0, 2), (-1, 0, 2), |
|
(1, 0, -2), (-1, 0, -2), (1, 2, 0), (-1, 2, 0), (1, -2, 0), |
|
(-1, -2, 0), (2, 0, 1), (-2, 0, 1), (2, 0, -1), (-2, 0, -1), |
|
(2, 1, 0), (-2, 1, 0), (2, -1, 0), (-2, -1, 0)] |
|
""" |
|
return (type(t)(i) for j in multiset_permutations(t) |
|
for i in permute_signs(j)) |
|
|
|
|
|
def rotations(s, dir=1): |
|
"""Return a generator giving the items in s as list where |
|
each subsequent list has the items rotated to the left (default) |
|
or right (``dir=-1``) relative to the previous list. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import rotations |
|
>>> list(rotations([1,2,3])) |
|
[[1, 2, 3], [2, 3, 1], [3, 1, 2]] |
|
>>> list(rotations([1,2,3], -1)) |
|
[[1, 2, 3], [3, 1, 2], [2, 3, 1]] |
|
""" |
|
seq = list(s) |
|
for i in range(len(seq)): |
|
yield seq |
|
seq = rotate_left(seq, dir) |
|
|
|
|
|
def roundrobin(*iterables): |
|
"""roundrobin recipe taken from itertools documentation: |
|
https://docs.python.org/3/library/itertools.html#itertools-recipes |
|
|
|
roundrobin('ABC', 'D', 'EF') --> A D E B F C |
|
|
|
Recipe credited to George Sakkis |
|
""" |
|
nexts = cycle(iter(it).__next__ for it in iterables) |
|
|
|
pending = len(iterables) |
|
while pending: |
|
try: |
|
for nxt in nexts: |
|
yield nxt() |
|
except StopIteration: |
|
pending -= 1 |
|
nexts = cycle(islice(nexts, pending)) |
|
|
|
|
|
|
|
class NotIterable: |
|
""" |
|
Use this as mixin when creating a class which is not supposed to |
|
return true when iterable() is called on its instances because |
|
calling list() on the instance, for example, would result in |
|
an infinite loop. |
|
""" |
|
pass |
|
|
|
|
|
def iterable(i, exclude=(str, dict, NotIterable)): |
|
""" |
|
Return a boolean indicating whether ``i`` is SymPy iterable. |
|
True also indicates that the iterator is finite, e.g. you can |
|
call list(...) on the instance. |
|
|
|
When SymPy is working with iterables, it is almost always assuming |
|
that the iterable is not a string or a mapping, so those are excluded |
|
by default. If you want a pure Python definition, make exclude=None. To |
|
exclude multiple items, pass them as a tuple. |
|
|
|
You can also set the _iterable attribute to True or False on your class, |
|
which will override the checks here, including the exclude test. |
|
|
|
As a rule of thumb, some SymPy functions use this to check if they should |
|
recursively map over an object. If an object is technically iterable in |
|
the Python sense but does not desire this behavior (e.g., because its |
|
iteration is not finite, or because iteration might induce an unwanted |
|
computation), it should disable it by setting the _iterable attribute to False. |
|
|
|
See also: is_sequence |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import iterable |
|
>>> from sympy import Tuple |
|
>>> things = [[1], (1,), set([1]), Tuple(1), (j for j in [1, 2]), {1:2}, '1', 1] |
|
>>> for i in things: |
|
... print('%s %s' % (iterable(i), type(i))) |
|
True <... 'list'> |
|
True <... 'tuple'> |
|
True <... 'set'> |
|
True <class 'sympy.core.containers.Tuple'> |
|
True <... 'generator'> |
|
False <... 'dict'> |
|
False <... 'str'> |
|
False <... 'int'> |
|
|
|
>>> iterable({}, exclude=None) |
|
True |
|
>>> iterable({}, exclude=str) |
|
True |
|
>>> iterable("no", exclude=str) |
|
False |
|
|
|
""" |
|
if hasattr(i, '_iterable'): |
|
return i._iterable |
|
try: |
|
iter(i) |
|
except TypeError: |
|
return False |
|
if exclude: |
|
return not isinstance(i, exclude) |
|
return True |
|
|
|
|
|
def is_sequence(i, include=None): |
|
""" |
|
Return a boolean indicating whether ``i`` is a sequence in the SymPy |
|
sense. If anything that fails the test below should be included as |
|
being a sequence for your application, set 'include' to that object's |
|
type; multiple types should be passed as a tuple of types. |
|
|
|
Note: although generators can generate a sequence, they often need special |
|
handling to make sure their elements are captured before the generator is |
|
exhausted, so these are not included by default in the definition of a |
|
sequence. |
|
|
|
See also: iterable |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.utilities.iterables import is_sequence |
|
>>> from types import GeneratorType |
|
>>> is_sequence([]) |
|
True |
|
>>> is_sequence(set()) |
|
False |
|
>>> is_sequence('abc') |
|
False |
|
>>> is_sequence('abc', include=str) |
|
True |
|
>>> generator = (c for c in 'abc') |
|
>>> is_sequence(generator) |
|
False |
|
>>> is_sequence(generator, include=(str, GeneratorType)) |
|
True |
|
|
|
""" |
|
return (hasattr(i, '__getitem__') and |
|
iterable(i) or |
|
bool(include) and |
|
isinstance(i, include)) |
|
|
|
|
|
@deprecated( |
|
""" |
|
Using postorder_traversal from the sympy.utilities.iterables submodule is |
|
deprecated. |
|
|
|
Instead, use postorder_traversal from the top-level sympy namespace, like |
|
|
|
sympy.postorder_traversal |
|
""", |
|
deprecated_since_version="1.10", |
|
active_deprecations_target="deprecated-traversal-functions-moved") |
|
def postorder_traversal(node, keys=None): |
|
from sympy.core.traversal import postorder_traversal as _postorder_traversal |
|
return _postorder_traversal(node, keys=keys) |
|
|
|
|
|
@deprecated( |
|
""" |
|
Using interactive_traversal from the sympy.utilities.iterables submodule |
|
is deprecated. |
|
|
|
Instead, use interactive_traversal from the top-level sympy namespace, |
|
like |
|
|
|
sympy.interactive_traversal |
|
""", |
|
deprecated_since_version="1.10", |
|
active_deprecations_target="deprecated-traversal-functions-moved") |
|
def interactive_traversal(expr): |
|
from sympy.interactive.traversal import interactive_traversal as _interactive_traversal |
|
return _interactive_traversal(expr) |
|
|
|
|
|
@deprecated( |
|
""" |
|
Importing default_sort_key from sympy.utilities.iterables is deprecated. |
|
Use from sympy import default_sort_key instead. |
|
""", |
|
deprecated_since_version="1.10", |
|
active_deprecations_target="deprecated-sympy-core-compatibility", |
|
) |
|
def default_sort_key(*args, **kwargs): |
|
from sympy import default_sort_key as _default_sort_key |
|
return _default_sort_key(*args, **kwargs) |
|
|
|
|
|
@deprecated( |
|
""" |
|
Importing default_sort_key from sympy.utilities.iterables is deprecated. |
|
Use from sympy import default_sort_key instead. |
|
""", |
|
deprecated_since_version="1.10", |
|
active_deprecations_target="deprecated-sympy-core-compatibility", |
|
) |
|
def ordered(*args, **kwargs): |
|
from sympy import ordered as _ordered |
|
return _ordered(*args, **kwargs) |
|
|