class_MultisetHistogram(tuple):pass_N=-1_ITEMS=-2_M=slice(None,_ITEMS)def_multiset_histogram(n):"""Return tuple used in permutation and combination counting. Input is a dictionary giving items with counts as values or a sequence of items (which need not be sorted). The data is stored in a class deriving from tuple so it is easily recognized and so it can be converted easily to a list. """iftype(n)isdict:# item: countifnotall(isinstance(v,int)andv>=0forvinn.values()):raiseValueErrortot=sum(n.values())items=sum(1forkinnifn[k]>0)return_MultisetHistogram([n[k]forkinnifn[k]>0]+[items,tot])else:n=list(n)s=set(n)iflen(s)==len(n):n=[1]*len(n)n.extend([len(n),len(n)])return_MultisetHistogram(n)m=dict(zip(s,range(len(s))))d=dict(zip(range(len(s)),[0]*len(s)))foriinn:d[m[i]]+=1return_multiset_histogram(d)defnP(n,k=None,replacement=False):"""Return the number of permutations of ``n`` items taken ``k`` at a time. Possible values for ``n``:: integer - set of length ``n`` sequence - converted to a multiset internally multiset - {element: multiplicity} If ``k`` is None then the total of all permutations of length 0 through the number of items represented by ``n`` will be returned. If ``replacement`` is True then a given item can appear more than once in the ``k`` items. (For example, for 'ab' permutations of 2 would include 'aa', 'ab', 'ba' and 'bb'.) The multiplicity of elements in ``n`` is ignored when ``replacement`` is True but the total number of elements is considered since no element can appear more times than the number of elements in ``n``. Examples ======== >>> from sympy.functions.combinatorial.numbers import nP >>> from sympy.utilities.iterables import multiset_permutations, multiset >>> nP(3, 2) 6 >>> nP('abc', 2) == nP(multiset('abc'), 2) == 6 True >>> nP('aab', 2) 3 >>> nP([1, 2, 2], 2) 3 >>> [nP(3, i) for i in range(4)] [1, 3, 6, 6] >>> nP(3) == sum(_) True When ``replacement`` is True, each item can have multiplicity equal to the length represented by ``n``: >>> nP('aabc', replacement=True) 121 >>> [len(list(multiset_permutations('aaaabbbbcccc', i))) for i in range(5)] [1, 3, 9, 27, 81] >>> sum(_) 121 References ========== .. [1] http://en.wikipedia.org/wiki/Permutation See Also ======== sympy.utilities.iterables.multiset_permutations """try:n=as_int(n)exceptValueError:returnInteger(_nP(_multiset_histogram(n),k,replacement))returnInteger(_nP(n,k,replacement))@cacheitdef_nP(n,k=None,replacement=False):fromsympy.functions.combinatorial.factorialsimportfactorialfromsympy.core.mulimportprodifk==0:return1ifisinstance(n,SYMPY_INTS):# n different items# assert n >= 0ifkisNone:returnsum(_nP(n,i,replacement)foriinrange(n+1))elifreplacement:returnn**kelifk>n:return0elifk==n:returnfactorial(k)elifk==1:returnnelse:# assert k >= 0return_product(n-k+1,n)elifisinstance(n,_MultisetHistogram):ifkisNone:returnsum(_nP(n,i,replacement)foriinrange(n[_N]+1))elifreplacement:returnn[_ITEMS]**kelifk==n[_N]:returnfactorial(k)/prod([factorial(i)foriinn[_M]ifi>1])elifk>n[_N]:return0elifk==1:returnn[_ITEMS]else:# assert k >= 0tot=0n=list(n)foriinrange(len(n[_M])):ifnotn[i]:continuen[_N]-=1ifn[i]==1:n[i]=0n[_ITEMS]-=1tot+=_nP(_MultisetHistogram(n),k-1)n[_ITEMS]+=1n[i]=1else:n[i]-=1tot+=_nP(_MultisetHistogram(n),k-1)n[i]+=1n[_N]+=1returntot@cacheitdef_AOP_product(n):"""for n = (m1, m2, .., mk) return the coefficients of the polynomial, prod(sum(x**i for i in range(nj + 1)) for nj in n); i.e. the coefficients of the product of AOPs (all-one polynomials) or order given in n. The resulting coefficient corresponding to x**r is the number of r-length combinations of sum(n) elements with multiplicities given in n. The coefficients are given as a default dictionary (so if a query is made for a key that is not present, 0 will be returned). Examples ======== >>> from sympy.functions.combinatorial.numbers import _AOP_product >>> from sympy.abc import x >>> n = (2, 2, 3) # e.g. aabbccc >>> prod = ((x**2 + x + 1)*(x**2 + x + 1)*(x**3 + x**2 + x + 1)).expand() >>> c = _AOP_product(n); dict(c) {0: 1, 1: 3, 2: 6, 3: 8, 4: 8, 5: 6, 6: 3, 7: 1} >>> [c[i] for i in range(8)] == [prod.coeff(x, i) for i in range(8)] True The generating poly used here is the same as that listed in http://tinyurl.com/cep849r, but in a refactored form. """fromcollectionsimportdefaultdictn=list(n)ord=sum(n)need=(ord+2)//2rv=[1]*(n.pop()+1)rv.extend([0]*(need-len(rv)))rv=rv[:need]whilen:ni=n.pop()N=ni+1was=rv[:]foriinrange(1,min(N,len(rv))):rv[i]+=rv[i-1]foriinrange(N,need):rv[i]+=rv[i-1]-was[i-N]rev=list(reversed(rv))iford%2:rv=rv+revelse:rv[-1:]=revd=defaultdict(int)foriinrange(len(rv)):d[i]=rv[i]returnddefnC(n,k=None,replacement=False):"""Return the number of combinations of ``n`` items taken ``k`` at a time. Possible values for ``n``:: integer - set of length ``n`` sequence - converted to a multiset internally multiset - {element: multiplicity} If ``k`` is None then the total of all combinations of length 0 through the number of items represented in ``n`` will be returned. If ``replacement`` is True then a given item can appear more than once in the ``k`` items. (For example, for 'ab' sets of 2 would include 'aa', 'ab', and 'bb'.) The multiplicity of elements in ``n`` is ignored when ``replacement`` is True but the total number of elements is considered since no element can appear more times than the number of elements in ``n``. Examples ======== >>> from sympy.functions.combinatorial.numbers import nC >>> from sympy.utilities.iterables import multiset_combinations >>> nC(3, 2) 3 >>> nC('abc', 2) 3 >>> nC('aab', 2) 2 When ``replacement`` is True, each item can have multiplicity equal to the length represented by ``n``: >>> nC('aabc', replacement=True) 35 >>> [len(list(multiset_combinations('aaaabbbbcccc', i))) for i in range(5)] [1, 3, 6, 10, 15] >>> sum(_) 35 If there are ``k`` items with multiplicities ``m_1, m_2, ..., m_k`` then the total of all combinations of length 0 hrough ``k`` is the product, ``(m_1 + 1)*(m_2 + 1)*...*(m_k + 1)``. When the multiplicity of each item is 1 (i.e., k unique items) then there are 2**k combinations. For example, if there are 4 unique items, the total number of combinations is 16: >>> sum(nC(4, i) for i in range(5)) 16 References ========== .. [1] http://en.wikipedia.org/wiki/Combination .. [2] http://tinyurl.com/cep849r See Also ======== sympy.utilities.iterables.multiset_combinations """fromsympy.functions.combinatorial.factorialsimportbinomialfromsympy.core.mulimportprodifisinstance(n,SYMPY_INTS):ifkisNone:ifnotreplacement:return2**nreturnsum(nC(n,i,replacement)foriinrange(n+1))assertk>=0ifreplacement:returnbinomial(n+k-1,k)returnbinomial(n,k)ifisinstance(n,_MultisetHistogram):N=n[_N]ifkisNone:ifnotreplacement:returnprod(m+1forminn[_M])returnsum(nC(n,i,replacement)foriinrange(N+1))elifreplacement:returnnC(n[_ITEMS],k,replacement)# assert k >= 0elifkin(1,N-1):returnn[_ITEMS]elifkin(0,N):return1return_AOP_product(tuple(n[_M]))[k]else:returnnC(_multiset_histogram(n),k,replacement)@cacheitdef_stirling1(n,k):ifn==k==0:returnS.Oneif0in(n,k):returnS.Zeron1=n-1# some special valuesifn==k:returnS.Oneelifk==1:returnfactorial(n1)elifk==n1:returnC.binomial(n,2)elifk==n-2:return(3*n-1)*C.binomial(n,3)/4elifk==n-3:returnC.binomial(n,2)*C.binomial(n,4)# general recurrencereturnn1*_stirling1(n1,k)+_stirling1(n1,k-1)@cacheitdef_stirling2(n,k):ifn==k==0:returnS.Oneif0in(n,k):returnS.Zeron1=n-1# some special valuesifk==n1:returnC.binomial(n,2)elifk==2:return2**n1-1# general recurrencereturnk*_stirling2(n1,k)+_stirling2(n1,k-1)

@cacheitdef_nT(n,k):"""Return the partitions of ``n`` items into ``k`` parts. This is used by ``nT`` for the case when ``n`` is an integer."""ifk==0:return1ifk==nelse0returnsum(_nT(n-k,j)forjinrange(min(k,n-k)+1))defnT(n,k=None):"""Return the number of ``k``-sized partitions of ``n`` items. Possible values for ``n``:: integer - ``n`` identical items sequence - converted to a multiset internally multiset - {element: multiplicity} Note: the convention for ``nT`` is different than that of ``nC`` and``nP`` in that here an integer indicates ``n`` *identical* items instead of a set of length ``n``; this is in keepng with the ``partitions`` function which treats its integer-``n`` input like a list of ``n`` 1s. One can use ``range(n)`` for ``n`` to indicate ``n`` distinct items. If ``k`` is None then the total number of ways to partition the elements represented in ``n`` will be returned. Examples ======== >>> from sympy.functions.combinatorial.numbers import nT Partitions of the given multiset: >>> [nT('aabbc', i) for i in range(1, 7)] [1, 8, 11, 5, 1, 0] >>> nT('aabbc') == sum(_) True (TODO The following can be activated with >>> when taocp_multiset_permutation is in place.) >> [nT("mississippi", i) for i in range(1, 12)] [1, 74, 609, 1521, 1768, 1224, 579, 197, 50, 9, 1] Partitions when all items are identical: >>> [nT(5, i) for i in range(1, 6)] [1, 2, 2, 1, 1] >>> nT('1'*5) == sum(_) True When all items are different: >>> [nT(range(5), i) for i in range(1, 6)] [1, 15, 25, 10, 1] >>> nT(range(5)) == sum(_) True References ========== .. [1] http://undergraduate.csse.uwa.edu.au/units/CITS7209/partition.pdf See Also ======== sympy.utilities.iterables.partitions sympy.utilities.iterables.multiset_partitions """fromsympy.utilities.iterablesimportmultiset_partitionsifisinstance(n,SYMPY_INTS):# assert n >= 0# all the sameifkisNone:returnsum(_nT(n,k)forkinrange(1,n+1))return_nT(n,k)ifnotisinstance(n,_MultisetHistogram):try:# if n contains hashable items there is some# quick handling that can be doneu=len(set(n))ifu==1:returnnT(len(n),k)elifu==len(n):n=range(u)raiseTypeErrorexceptTypeError:n=_multiset_histogram(n)N=n[_N]ifkisNoneandN==1:return1ifkin(1,N):return1ifk==2orN==2andkisNone:m,r=divmod(N,2)rv=sum(nC(n,i)foriinrange(1,m+1))ifnotr:rv-=nC(n,m)//2ifkisNone:rv+=1# for k == 1returnrvifN==n[_ITEMS]:# all distinctifkisNone:returnbell(N)returnstirling(N,k)ifkisNone:returnsum(nT(n,k)forkinrange(1,N+1))tot=0forpinmultiset_partitions([ifori,jinenumerate(n[_M])foriiinrange(j)]):tot+=len(p)==kreturntot