Comments on: Help me tackle this challenging math/programming problem from a contest. http://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest/
Comments on Ask MetaFilter post Help me tackle this challenging math/programming problem from a contest.Tue, 20 Nov 2012 23:30:20 -0800Wed, 21 Nov 2012 00:07:35 -0800en-ushttp://blogs.law.harvard.edu/tech/rss60Question: Help me tackle this challenging math/programming problem from a contest.http://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest
Help me tackle this challenging math/programming problem from a contest. <br /><br /> I was looking at some problems from an old programming contest and this one struck me. Say you're given two numbers, a and z. What you have to do is output every possible partition of a into z coprime numbers, with the numbers within the partitions in non-decreasing order and the partitions themselves in ascending order (if a group of numbers is coprime, then the gcd of any two of them is 1). Order between the partitions is like alphabetic order, but with numbers, so for example if a=40 and z=6, then you would output 1 1 1 1 1 35 first, then 1 1 1 1 5 31, then 1 1 1 1 7 29 and so on. For a=8 and z=3, you would just have the partitions 1 1 6, then 1 2 5, and finally 1 3 4. You can see the first partition will always begin with z-1 ones and end with the number a-z-1. I'm pretty much at a total loss for any kind of general solution though...post:ask.metafilter.com,2012:site.229360Tue, 20 Nov 2012 23:30:20 -0800bookman117hardprogrammingproblemhardmathproblemcoprimenumbersgcdBy: ethiddahttp://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest#3319043
I'm typing on my phone and it's just before bed time so this is the brute force solution, but probably not the optimal solution (though you can increase it greatly using memorization).<br>
<br>
This seems like a recursive problem to me. That is, if function f(a, z) solves the problem, it would do something like this:<br>
<br>
f(a, z, primes = empty list) returns list of coprime partitions as described {<br>
for each number i between 1 and a {<br>
if i is coprime with all numbers in primes {<br>
partial result = f(a-I, z-1, primes.append(i))<br>
for r in partial result {<br>
add result r. append (i)<br>
}<br>
}<br>
}<br>
sort results list<br>
return results list<br>
}<br>
<br>
Three things are left to do, but they are relatively simple:<br>
<br>
A. How to tell if i is a coprime with numbers in a given list.<br>
B. How to sort the results list as described.<br>
C. How to optimize, including memorization, sorting at the end, or just better algorithms.comment:ask.metafilter.com,2012:site.229360-3319043Wed, 21 Nov 2012 00:07:35 -0800ethiddaBy: Unexpected Indenthttp://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest#3319046
Not sure how to efficiently determine coprimes, but I think you can generate the solutions recursively like this (python)<br>
<pre><br>
def partition(n, buckets):<br>
if buckets == 1:<br>
yield [n]<br>
elif n &gt;= buckets:<br>
for i in range(1, n):<br>
for rest in partition(n - i, buckets - 1):<br>
yield [i] + rest<br>
<br>
def solutions(n, buckets):<br>
return filter(all_coprime, partition(n, buckets))<br>
</pre><br>
<br>
This gives me these for 8, 3:<br>
[1, 1, 6]<br>
[1, 2, 5]<br>
[1, 3, 4]<br>
[1, 4, 3]<br>
[1, 5, 2]<br>
[1, 6, 1]<br>
[2, 1, 5]<br>
[2, 5, 1]<br>
[3, 1, 4]<br>
[3, 4, 1]<br>
[4, 1, 3]<br>
[4, 3, 1]<br>
[5, 1, 2]<br>
[5, 2, 1]<br>
[6, 1, 1]comment:ask.metafilter.com,2012:site.229360-3319046Wed, 21 Nov 2012 00:24:50 -0800Unexpected IndentBy: pharmhttp://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest#3319067
Note that it's easy to generate all possible co-prime pairs of numbers (see the wikipedia page) which is probably the first step to an efficient solution to this problem.comment:ask.metafilter.com,2012:site.229360-3319067Wed, 21 Nov 2012 02:38:55 -0800pharmBy: flabdablethttp://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest#3319146
Seems to me that the sorting requirement is going to cut down the number of candidates pretty quick, via a kind of combinatorial implosion effect; Unexpected Indent's list of 15 solutions is reduced to the required 3 if only the sorted variants are selected, and that's with only 3 partitions.<br>
<br>
So I'd start with an algorithm designed to generate the partitionings in an inherently sorted order, then find a good place within it to add logic to filter for the coprime property. If the first pair of numbers tested for coprimeness at each step were the ones most recently modified by the partitioning generator, then most of the time I'd expect the filter to reject candidate partitionings with very few tests each.comment:ask.metafilter.com,2012:site.229360-3319146Wed, 21 Nov 2012 06:16:49 -0800flabdabletBy: whatnoteverhttp://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest#3319256
I took Unexpected Indent's <a href="http://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest#3319046">solution</a> and added the missing pieces:<br>
<br>
<pre><br>
from fractions import gcd<br>
import sys<br>
<br>
def partition(n, buckets, min=1):<br>
if n &lt; min:<br>
return<br>
if buckets == 1:<br>
yield [n]<br>
elif n &gt;= buckets:<br>
for i in range(min, n):<br>
for rest in partition(n - i, buckets - 1, i):<br>
yield [i] + rest<br>
<br>
def all_coprime(values):<br>
for i in range(len(values)):<br>
for j in range(i+1, len(values)):<br>
if gcd(values[i], values[j]) &gt; 1:<br>
return False<br>
return True<br>
<br>
def solutions(n, buckets):<br>
return filter(all_coprime, partition(n, buckets))<br>
<br>
def main():<br>
n = int(sys.argv[1])<br>
buckets = int(sys.argv[2])<br>
for sol in solutions(n, buckets):<br>
print sol<br>
<br>
main()<br>
</pre><br>
<br>
I added a bit to the partition() function to only generate the increasing-order partitions (and it already generated the partitions themselves in the correct order). The coprime check is just brute-force, though, and there is certainly a faster way to do that, if needed. Only adding elements to a growing partition that are coprime with the values already included would probably be much more efficient. There are probably smarter ways to generate the partitions (avoiding lots of "dead ends") as well.comment:ask.metafilter.com,2012:site.229360-3319256Wed, 21 Nov 2012 07:50:50 -0800whatnoteverBy: whatnoteverhttp://ask.metafilter.com/229360/Help-me-tackle-this-challenging-mathprogramming-problem-from-a-contest#3319440
... and after some improvements (purely for efficiency):<br>
<br>
<pre><br>
from fractions import gcd<br>
import sys<br>
<br>
coprimes = []<br>
<br>
def partition(n, buckets, allowed):<br>
global coprimes<br>
<br>
if buckets == 1: # Have to include n at this point<br>
if n in allowed:<br>
yield [n]<br>
return<br>
<br>
for i in allowed:<br>
# Prune remaining search if no solution is possible<br>
if i*buckets &gt; n:<br>
return<br>
# Remove values that can't be used any more if we include i<br>
newallowed = [ x for x in allowed if ((i &lt;= x &lt;= n-i) and coprimes[i][x]) ]<br>
for rest in partition(n - i, buckets - 1, newallowed):<br>
yield [i] + rest<br>
<br>
def main():<br>
n = int(sys.argv[1])<br>
buckets = int(sys.argv[2])<br>
<br>
# Precalculate GCDs<br>
global coprimes<br>
for i in range(n+1):<br>
coprimes.append([False]*(n+1))<br>
for j in range(i,n+1):<br>
coprimes[i][j] = (gcd(i,j) == 1)<br>
<br>
for sol in partition(n, buckets, allowed=range(1,n+1)):<br>
print sol<br>
<br>
main()<br>
</pre><br>
<br>
This does what I described above, maintaining a list of values that are still coprime with all earlier values in order to avoid a lot of dead ends. It also removes values that cannot be used due to being less than a value chosen already (i) or more than the amount that remains (n-i). On top of that, it prunes further by stopping whenever no solution can exist because the remaining choices would have to sum to more than n. With any sort of branching, recursive algorithm, pruning is really important. Any time you can prove that a given branch will produce no solutions, you can prune it and save time.<br>
<br>
This version also precalculates GCDs to avoid computing the same results over and over. That part could be optimized as well, though it's fast enough for n &lt; 2000 or so.<br>
<br>
Overall, one of the best ways to improve an algorithm's efficiency is to avoid redundant or unnecessary work. The improvements in this version do exactly that, and they reduce the runtime immensely for harder instances.</></pre>comment:ask.metafilter.com,2012:site.229360-3319440Wed, 21 Nov 2012 10:22:22 -0800whatnotever