Integer partition with python and erlang

In number theory and combinatorics, a partition of a positive integer n, also called an integer partition, is a way of writing n as a sum of positive integers. Two sums that differ only in the order of their summands are considered the same partition.

For example, 4 can be partitioned in four distinct ways:

3 + 1
2 + 2
2 + 1 + 1
1 + 1 + 1 + 1

More information you can find here and here.

Recursive algorithm with python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import itertools
import uuid

#
# partitioning for integer <<n>>
#
def p0(n, permutate=False, first=True):
    answer = set()
    if not first: answer.add((n,))
    for x in range(1, n):
        for y in p0(n - x, first=False): answer.add(tuple(sorted((x,)+y)))
    if permutate:
        return sum([list(set(list(itertools.permutations(i)))) for i in answer],[])
    else:
        return answer

Recursive algorith with erlang:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
p0(N) -> lists:usort([lists:sort(E)||E<-fl(p0_(N,N,[]))]).
p0_(1,_, _Opts)  -> [[1]];
p0_(N,M, _Opts ) -> 
    Extra = [ [lists:sort(Y++[X])||Y<-p0_(N-X,M, _Opts)] || X<-lists:seq(1,N-1)],
    if N == M -> Extra; true -> [[N]|Extra] end.

fl([])    -> [];
fl([H|T]=L) when is_integer(H),is_list(T) ->
    case is_flat(T) of
        true  -> [lists:flatten(L)];
        false -> fl([expl(H,E) || E <- T]) 
    end;
fl([H|T]) -> fl(H) ++ fl(T);
fl(H)     -> [H].

expl(A,B) when is_list(B) -> [A|B];
expl(_,B) -> B.

is_flat(L) when is_list(L) -> length([E||E<-L, is_list(E)]) < 2;
is_flat(_) -> true. 

Another implementation with erlang (using process dictionary). Much faster for big numbers.

1
2
3
4
5
6
7
8
9
10
11
12
p0_1(N)    -> p0_1_(N,N).
p0_1_(1,_) -> [[1]];
p0_1_(N,M) -> 
     Answer = make_ref(),
     if N =/= M -> put(Answer,[[N]]); true -> put(Answer, []) end,
     lists:foreach(fun(X)-> 
         lists:foreach(fun(Y)-> R = get(Answer)++[lists:sort(Y++[X])], put(Answer,R) 
                       end, p0_1_(N-X,M))
     end, lists:seq(1,N-1)),
     Ret = get(Answer), erase(Answer),
     lists:usort(Ret).

If we are test performance of p0 and p0_1 for N=25 with 100000 iterations:

1
2
3
4
    {Time1,_} = timer:tc(fun(_)-> p0(N) end, [100000]),
    io:format("Time1:~p ~n", [Time1]),
    {Time2,_} = timer:tc(fun(_)-> p0_1(N) end, [100000]),
    io:format("Time2:~p ~n", [Time2]).

On my laptopn it will be (time in microseconds):

Time1:184666120 
Time2:30488624

If denominators stricted by given list, for example we can use only 1 and 2 for combinations we have to make some changes. Python implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#
# partitioning for integer <<n>> with denominators from list <<ds>>
#
def p1(n, ds, permutate=False, first=True):
    answer = set()
    if not first and n in ds: answer.add((n,))
    for x in range(1, n):
        if x in ds:
            for y in p1(n - x, ds, first=False): answer.add(tuple(sorted((x,)+y)))
    if permutate:
        return sum([list(set(list(itertools.permutations(i)))) for i in answer],[])
    else:
        return answer

and same algorithm with erlang:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
%
% partitioning for integer <<N>> with denominators from list <<DS>>
%
p1(N,   DS) -> lists:filter(fun(L)-> lists:sum(L)==N end,lists:usort([lists:sort(E)||E<-fl(p1_(N,N,DS))])).
p1_(1,_,DS) -> case lists:member(1,DS) of true -> [[1]];_ -> [] end;
p1_(N,M,DS) ->
    Extra = [ [lists:sort(Y++[X])||Y<-p1_(N-X,M,DS), lists:member(X,DS)] || X<-lists:seq(1,N-1)],
    case {N==M, lists:member(N,DS)} of
        {false, true} -> [[N]|Extra]
        ;_ -> Extra
    end.

fl([])    -> [];
fl([H|T]=L) when is_integer(H),is_list(T) ->
    case is_flat(T) of
        true  -> [lists:flatten(L)];
        false -> fl([expl(H,E) || E <- T]) 
    end;
fl([H|T]) -> fl(H) ++ fl(T);
fl(H)     -> [H].

expl(A,B) when is_list(B) -> [A|B];
expl(_,B) -> B.

is_flat(L) when is_list(L) -> length([E||E<-L, is_list(E)]) < 2;
is_flat(_) -> true. 

And another limitation: if we are have limited numbers of denominators:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#
# partitioning for integer <<n>> with limited number of denominators from list <<ds>>
#
def p2(n, ds, permutate=False, first=True):
    answer = set()
    if not first and n in ds: 
        answer.add((n,))
        ds.remove(n)
    for x in range(1, n):
        if x in ds:
            ds.remove(x)
            for y in p2(n - x, list(ds), first=False): answer.add(tuple(sorted((x,)+y)))
    if permutate:
        return sum([list(set(list(itertools.permutations(i)))) for i in answer],[])
    else:
        return answer