How to find multiplicative partitions of any integer?
Asked Answered
L

3

15

I'm looking for an efficient algorithm for computing the multiplicative partitions for any given integer. For example, the number of such partitions for 12 is 4, which are

12 = 12 x 1 = 4 x 3 = 2 x 2 x 3 = 2 x 6

I've read the wikipedia article for this, but that doesn't really give me an algorithm for generating the partitions (it only talks about the number of such partitions, and to be honest, even that is not very clear to me!).

The problem I'm looking at requires me to compute multiplicative partitions for very large numbers (> 1 billion), so I was trying to come up with a dynamic programming approach for it (so that finding all possible partitions for a smaller number can be re-used when that smaller number is itself a factor of a bigger number), but so far, I don't know where to begin!

Any ideas/hints would be appreciated - this is not a homework problem, merely something I'm trying to solve because it seems so interesting!

Lorislorita answered 19/12, 2011 at 7:27 Comment(5)
With close votes, there ideally should be some sort of an explanation as to why you think this needs to be closed, so that the OP can rectify his/her mistakes (if any). Can the lone close voter please speak up ?Lorislorita
Close votes without any explanations - always loved those!Lorislorita
I cast a close vote in error. My apologies.Robinetta
@Mods, is there any way to redact close votes cast in error?Lorislorita
shan23, it really should not pose a problem; it would take a few more votes for this question to be closed. If that does happen, I will cast a reopen vote ASAP.Robinetta
F
6

Of course, the first thing to do is find the prime factorisation of the number, like glowcoder said. Say

n = p^a * q^b * r^c * ...

Then

  1. find the multiplicative partitions of m = n / p^a
  2. for 0 <= k <= a, find the multiplicative partitions of p^k, which is equivalent to finding the additive partitions of k
  3. for each multiplicative partition of m, find all distinct ways to distribute a-k factors p among the factors
  4. combine results of 2. and 3.

It is convenient to treat the multiplicative partitions as lists (or sets) of (divisor, multiplicity) pairs to avoid producing duplicates.

I've written the code in Haskell because it's the most convenient and concise of the languages I know for this sort of thing:

module MultiPart (multiplicativePartitions) where

import Data.List (sort)
import Math.NumberTheory.Primes (factorise)
import Control.Arrow (first)

multiplicativePartitions :: Integer -> [[Integer]]
multiplicativePartitions n
    | n < 1     = []
    | n == 1    = [[]]
    | otherwise = map ((>>= uncurry (flip replicate)) . sort) . pfPartitions $ factorise n

additivePartitions :: Int -> [[(Int,Int)]]
additivePartitions 0 = [[]]
additivePartitions n
    | n < 0     = []
    | otherwise = aParts n n
      where
        aParts :: Int -> Int -> [[(Int,Int)]]
        aParts 0 _ = [[]]
        aParts 1 m = [[(1,m)]]
        aParts k m = withK ++ aParts (k-1) m
          where
            withK = do
                let q = m `quot` k
                j <- [q,q-1 .. 1]
                [(k,j):prt | let r = m - j*k, prt <- aParts (min (k-1) r) r]

countedPartitions :: Int -> Int -> [[(Int,Int)]]
countedPartitions 0     count = [[(0,count)]]
countedPartitions quant count = cbParts quant quant count
  where
    prep _ 0 = id
    prep m j = ((m,j):)
    cbParts :: Int -> Int -> Int -> [[(Int,Int)]]
    cbParts q 0 c
        | q == 0    = if c == 0 then [[]] else [[(0,c)]]
        | otherwise = error "Oops"
    cbParts q 1 c
        | c < q     = []        -- should never happen
        | c == q    = [[(1,c)]]
        | otherwise = [[(1,q),(0,c-q)]]
    cbParts q m c = do
        let lo = max 0 $ q - c*(m-1)
            hi = q `quot` m
        j <- [lo .. hi]
        let r = q - j*m
            m' = min (m-1) r
        map (prep m j) $ cbParts r m' (c-j)

primePowerPartitions :: Integer -> Int -> [[(Integer,Int)]]
primePowerPartitions p e = map (map (first (p^))) $ additivePartitions e

distOne :: Integer -> Int -> Integer -> Int -> [[(Integer,Int)]]
distOne _ 0 d k = [[(d,k)]]
distOne p e d k = do
    cap <- countedPartitions e k
    return $ [(p^i*d,m) | (i,m) <- cap]

distribute :: Integer -> Int -> [(Integer,Int)] -> [[(Integer,Int)]]
distribute _ 0 xs = [xs]
distribute p e [(d,k)] = distOne p e d k
distribute p e ((d,k):dks) = do
    j <- [0 .. e]
    dps <- distOne p j d k
    ys <- distribute p (e-j) dks
    return $ dps ++ ys
distribute _ _ [] = []

pfPartitions :: [(Integer,Int)] -> [[(Integer,Int)]]
pfPartitions [] = [[]]
pfPartitions [(p,e)] = primePowerPartitions p e
pfPartitions ((p,e):pps) = do
    cop <- pfPartitions pps
    k <- [0 .. e]
    ppp <- primePowerPartitions p k
    mix <- distribute p (e-k) cop
    return (ppp ++ mix)

It's not particularly optimised, but it does the job.

Some times and results:

Prelude MultiPart> length $ multiplicativePartitions $ 10^10
59521
(0.03 secs, 53535264 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ 10^11
151958
(0.11 secs, 125850200 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ 10^12
379693
(0.26 secs, 296844616 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 10]
70520
(0.07 secs, 72786128 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 11]
425240
(0.36 secs, 460094808 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 12]
2787810
(2.06 secs, 2572962320 bytes)

The 10^k are of course particularly easy because there are only two primes involved (but squarefree numbers are still easier), the factorials get slow earlier. I think by careful organisation of the order and choice of better data structures than lists, there's quite a bit to be gained (probably one should sort the prime factors by exponent, but I don't know whether one should start with the highest exponents or the lowest).

Fabria answered 21/12, 2011 at 3:41 Comment(6)
I don't know Haskell, but I assume you've run your code - I was interested in knowing what kind of time does it take for large numbers (say ~10,000,000,000)? It would give me an idea of what to expect when I finally code my solution in C++...Lorislorita
@shan23 I've added some timings. As a wild guess, a factor of 10 improvement doesn't look impossible.Fabria
Thats a really great answer (with the timings) - let me try out on C++ over the weekend, and see if it gets better. Also, a related query - how would one utilize the partitions for $n$ when calculating the partitions for a bigger number, one of whose factors is $n$? I'm looking to get the partitions of a range of numbers n...m, so this would be particularly useful to me if I can figure out a way for that!Lorislorita
Since the shape of the partitions depends only on the exponents in the prime factorisation, we can also reuse the partitions of n to generate the partitions of m if n is not a divisor of m, all we need is a suitable structure of the prime factorisation. For example, 12 = 2^2 * 3^1, so we can reuse the partitions of 12 to find the partitions of 90 = 2^1 * 3^2 * 5^1. We just have to exchange 2 and 3 in the partitions of 12 to get the partitions of 18, and then have to tack on the 5. If you store partitions for numbers of the form p^k, p^a * q^b, p^a * q^b *r^c, ... for ...Fabria
... all small enough exponents and up to four or five prime factors, you can reuse them. Of course, I didn't mean to write 'partitions', it should have been 'partition templates', so e.g. (2,1) stands for a number of the form p^2*q, the set of partitions is (p^2*q); (p^2, q); (p*q, p); (p, p, q), which could be stored as the template ([2,1]); ([2], [1]); ([1,1], [1]); ([1], [1],[1]), and whenever you have a number of that form,you only need to fill in p and q. And if you have a number p^2*q*r, fill p and q in that template and tack on r. But whether it's faster...?Fabria
Just accepted it as answer, as it seems I forgot to accept it as answer before - better late than never :)Lorislorita
D
6

The first thing I would do is get the prime factorization of the number.

From there, I can make a permutation of each subset of the factors, multiplied by the remaining factors at that iteration.

So if you take a number like 24, you get

2 * 2 * 2 * 3 // prime factorization
a   b   c   d
// round 1
2 * (2 * 2 * 3) a * bcd
2 * (2 * 2 * 3) b * acd (removed for being dup)
2 * (2 * 2 * 3) c * abd (removed for being dup)
3 * (2 * 2 * 2) d * abc

Repeat for all "rounds" (round being the number of factors in the first number of the multiplication), removing duplicates as they come up.

So you end up with something like

// assume we have the prime factorization 
// and a partition set to add to
for(int i = 1; i < factors.size; i++) {
    for(List<int> subset : factors.permutate(2)) {
        List<int> otherSubset = factors.copy().remove(subset);
        int subsetTotal = 1;
        for(int p : subset) subsetTotal *= p;
        int otherSubsetTotal = 1;
        for(int p : otherSubset) otherSubsetTotal *= p;
        // assume your partition excludes if it's a duplicate
        partition.add(new FactorSet(subsetTotal,otherSubsetTotal));
    }
}
Duckworth answered 19/12, 2011 at 7:33 Comment(4)
permutation of the numbers that multiplication will add up to the original number.Ochone
(permutation? combintion? I forget the proper word) it should be permutation.Ochone
@glowcoder: Some issues - for a sufficiently large number which has a lot of prime factors, much of the work would be done in identifying and removing duplicate partitions. I was looking for a way to get past that during generation itself. Also, what does factors.permutate(2) do ? I didn't find any API in STL corresponding to that, hence was wondering about the significance of the "2" parameter.Lorislorita
@shan23 - there are some optimizations you can make to make it less damaging, but it is an inherently expensive operation as it is. The permutate(2) is a typo - it should be permutate(i) and no I don't believe it's in any STL library. It is pseudocode for a function that would return a list of list of values, each of size i, complete over all possible sublists.Duckworth
F
6

Of course, the first thing to do is find the prime factorisation of the number, like glowcoder said. Say

n = p^a * q^b * r^c * ...

Then

  1. find the multiplicative partitions of m = n / p^a
  2. for 0 <= k <= a, find the multiplicative partitions of p^k, which is equivalent to finding the additive partitions of k
  3. for each multiplicative partition of m, find all distinct ways to distribute a-k factors p among the factors
  4. combine results of 2. and 3.

It is convenient to treat the multiplicative partitions as lists (or sets) of (divisor, multiplicity) pairs to avoid producing duplicates.

I've written the code in Haskell because it's the most convenient and concise of the languages I know for this sort of thing:

module MultiPart (multiplicativePartitions) where

import Data.List (sort)
import Math.NumberTheory.Primes (factorise)
import Control.Arrow (first)

multiplicativePartitions :: Integer -> [[Integer]]
multiplicativePartitions n
    | n < 1     = []
    | n == 1    = [[]]
    | otherwise = map ((>>= uncurry (flip replicate)) . sort) . pfPartitions $ factorise n

additivePartitions :: Int -> [[(Int,Int)]]
additivePartitions 0 = [[]]
additivePartitions n
    | n < 0     = []
    | otherwise = aParts n n
      where
        aParts :: Int -> Int -> [[(Int,Int)]]
        aParts 0 _ = [[]]
        aParts 1 m = [[(1,m)]]
        aParts k m = withK ++ aParts (k-1) m
          where
            withK = do
                let q = m `quot` k
                j <- [q,q-1 .. 1]
                [(k,j):prt | let r = m - j*k, prt <- aParts (min (k-1) r) r]

countedPartitions :: Int -> Int -> [[(Int,Int)]]
countedPartitions 0     count = [[(0,count)]]
countedPartitions quant count = cbParts quant quant count
  where
    prep _ 0 = id
    prep m j = ((m,j):)
    cbParts :: Int -> Int -> Int -> [[(Int,Int)]]
    cbParts q 0 c
        | q == 0    = if c == 0 then [[]] else [[(0,c)]]
        | otherwise = error "Oops"
    cbParts q 1 c
        | c < q     = []        -- should never happen
        | c == q    = [[(1,c)]]
        | otherwise = [[(1,q),(0,c-q)]]
    cbParts q m c = do
        let lo = max 0 $ q - c*(m-1)
            hi = q `quot` m
        j <- [lo .. hi]
        let r = q - j*m
            m' = min (m-1) r
        map (prep m j) $ cbParts r m' (c-j)

primePowerPartitions :: Integer -> Int -> [[(Integer,Int)]]
primePowerPartitions p e = map (map (first (p^))) $ additivePartitions e

distOne :: Integer -> Int -> Integer -> Int -> [[(Integer,Int)]]
distOne _ 0 d k = [[(d,k)]]
distOne p e d k = do
    cap <- countedPartitions e k
    return $ [(p^i*d,m) | (i,m) <- cap]

distribute :: Integer -> Int -> [(Integer,Int)] -> [[(Integer,Int)]]
distribute _ 0 xs = [xs]
distribute p e [(d,k)] = distOne p e d k
distribute p e ((d,k):dks) = do
    j <- [0 .. e]
    dps <- distOne p j d k
    ys <- distribute p (e-j) dks
    return $ dps ++ ys
distribute _ _ [] = []

pfPartitions :: [(Integer,Int)] -> [[(Integer,Int)]]
pfPartitions [] = [[]]
pfPartitions [(p,e)] = primePowerPartitions p e
pfPartitions ((p,e):pps) = do
    cop <- pfPartitions pps
    k <- [0 .. e]
    ppp <- primePowerPartitions p k
    mix <- distribute p (e-k) cop
    return (ppp ++ mix)

It's not particularly optimised, but it does the job.

Some times and results:

Prelude MultiPart> length $ multiplicativePartitions $ 10^10
59521
(0.03 secs, 53535264 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ 10^11
151958
(0.11 secs, 125850200 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ 10^12
379693
(0.26 secs, 296844616 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 10]
70520
(0.07 secs, 72786128 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 11]
425240
(0.36 secs, 460094808 bytes)
Prelude MultiPart> length $ multiplicativePartitions $ product [2 .. 12]
2787810
(2.06 secs, 2572962320 bytes)

The 10^k are of course particularly easy because there are only two primes involved (but squarefree numbers are still easier), the factorials get slow earlier. I think by careful organisation of the order and choice of better data structures than lists, there's quite a bit to be gained (probably one should sort the prime factors by exponent, but I don't know whether one should start with the highest exponents or the lowest).

Fabria answered 21/12, 2011 at 3:41 Comment(6)
I don't know Haskell, but I assume you've run your code - I was interested in knowing what kind of time does it take for large numbers (say ~10,000,000,000)? It would give me an idea of what to expect when I finally code my solution in C++...Lorislorita
@shan23 I've added some timings. As a wild guess, a factor of 10 improvement doesn't look impossible.Fabria
Thats a really great answer (with the timings) - let me try out on C++ over the weekend, and see if it gets better. Also, a related query - how would one utilize the partitions for $n$ when calculating the partitions for a bigger number, one of whose factors is $n$? I'm looking to get the partitions of a range of numbers n...m, so this would be particularly useful to me if I can figure out a way for that!Lorislorita
Since the shape of the partitions depends only on the exponents in the prime factorisation, we can also reuse the partitions of n to generate the partitions of m if n is not a divisor of m, all we need is a suitable structure of the prime factorisation. For example, 12 = 2^2 * 3^1, so we can reuse the partitions of 12 to find the partitions of 90 = 2^1 * 3^2 * 5^1. We just have to exchange 2 and 3 in the partitions of 12 to get the partitions of 18, and then have to tack on the 5. If you store partitions for numbers of the form p^k, p^a * q^b, p^a * q^b *r^c, ... for ...Fabria
... all small enough exponents and up to four or five prime factors, you can reuse them. Of course, I didn't mean to write 'partitions', it should have been 'partition templates', so e.g. (2,1) stands for a number of the form p^2*q, the set of partitions is (p^2*q); (p^2, q); (p*q, p); (p, p, q), which could be stored as the template ([2,1]); ([2], [1]); ([1,1], [1]); ([1], [1],[1]), and whenever you have a number of that form,you only need to fill in p and q. And if you have a number p^2*q*r, fill p and q in that template and tack on r. But whether it's faster...?Fabria
Just accepted it as answer, as it seems I forgot to accept it as answer before - better late than never :)Lorislorita
O
0

Why dont you find all the numbers that can divide the number and then you find permutations of the numbers that multiplications will add up to the number?

Finding all numbers that can divide your number takes O(n).

Then you can permute this set to find all possible sets that multiplication of this set will give you the number.

Once you find set of all possible numbers that divide the original number, then you can do dynamic programming on them to find the set of numbers that multiplying them will give you the original number.

Ochone answered 19/12, 2011 at 7:34 Comment(1)
"Once you find set of all possible numbers that divide the original number, then you can do dynamic programming on them" - I was hoping for a more specific hint other than "doing dynamic programming" :). For instance, could you tell me how should I be using the partitions for a smaller integer while computing the partitions for a bigger integer ?Lorislorita

© 2022 - 2024 — McMap. All rights reserved.