From: Lie Ryan on
On 06/10/10 09:03, Bryan wrote:
> Lie Ryan wrote:
>> I went through the mathematical foundation of using
>> partition/distribution and inclusion-exclusion, and have written some
>> code that solves a subset of the problem, feel free if you or superpollo
>> are interested in continuing my answer (I won't be able to continue it
>> until next week, have been a little bit busy here)
>>
>> copying the code here for convenience:
>>
>> # memoization would be very useful here
>> def fact(n):
>> """ factorial function (i.e. n! = n * (n-1) * ... * 2 * 1) """
>> return n * fact(n - 1) if n != 0 else 1
>>
>> def C(n, r):
>> """ regular Combination (nCr) """
>> return fact(n) / (fact(n - r) * fact(r))
>>
>> def D(M, N):
>> """ Distribution aka Partitioning """
>> return C(M + N - 1, M)
>>
>> def partition10(M, i):
>> """ Count how many integer < N sums to M where N = 10**int(i) """
>> s = 0
>> sign = 1
>> for j in range(i + 1):
>> s += sign * D(M, i) * C(i, j)
>>
>> # flip the sign for inclusion-exclusion
>> sign *= -1
>>
>> # if M = 32, then 32, 22, 12, 2, -8
>> M -= 10
>> return s
>
> It doesn't seem to work. I get no answer at all, because it recursion-
> loops out when it calls fact() with a negative integer.

Why of course you're right! In my original post in comp.programming, I
used this definition of factorial:

def fact(n):
""" factorial function (i.e. n! = n * (n-1) * ... * 2 * 1) """
p = 1
for i in range(1,n+1):
p *= i
return p

when I reposted it here, I substituted that factorial to the recursive
definition which fails when n is negative without much testing and that
causes things to fails miserably. Sorry about that.
From: Bryan on
Lie Ryan wrote:
> In my original post in comp.programming, I
> used this definition of factorial:
>
> def fact(n):
>     """ factorial function (i.e. n! = n * (n-1) * ... * 2 * 1) """
>     p = 1
>     for i in range(1,n+1):
>         p *= i
>     return p

Ah, much better, but partition10(M, i) gets the wrong answer when i is
1 or 2. I think you don't want to let M go negative. With that tweak,
it seems to work in general, and fact() never gets called with a
negative number.

What I really like about your partition10() is that it's adaptable to
efficiently handle bases much larger than 10. Richard Thomas's
algorithm is poly-time and efficient as long as the base is small.

I'll take the liberty of tweaking your code to handle the 1 or 2 digit
case, and write the more general form. I'll also memoize fact(), and
add prttn() and a test.

--
--Bryan


_ft = [1]
def fact(n):
assert n >= 0 and n % 1 == 0
if len(_ft) <= n:
for i in range(len(_ft), n + 1):
_ft.append(i * _ft[-1])
return _ft[n]

def C(n, r):
""" regular Combination (nCr) """
return fact(n) // (fact(n - r) * fact(r))

def D(M, N):
""" Distribution aka Partitioning """
assert M >= 0 and N > 0
return C(M + N - 1, M)

def partition(nballs, nbins, binmax):
"""Count ways to put identical balls into distinct bounded bins.
"""
if nbins == 0:
return int(nballs == 0)
s = 0
sign = 1
for j in range(1 + min(nbins, nballs // binmax)):
s += sign * D(nballs, nbins) * C(nbins, j)

# flip the sign for inclusion-exclusion
sign *= -1
nballs -= binmax
return s

def prttn(m, n):
assert m >= 0 and n > 0
count = 0
dls = [int(c) for c in reversed(str(n))]
while dls:
msd = dls.pop()
count += sum(partition(m - d, len(dls), 10) for
d in range(min(msd, m + 1)))
m -= msd
return count


def test():
upto = 123456
counts = [0] * (len(str(upto)) * 9)
for n in range(upto):
digits = [int(c) for c in str(n)]
counts[sum(digits)] += 1
for m in range(9 * len(digits) + 2):
count = prttn(m, n + 1)
assert count == counts[m]
if count == 0:
break
assert count == 0
if n % 1000 == 0:
print('Tested to:', n)

test()