arraysalgorithmcombinationsrankinginteger-partition

Rank and unrank integer partition with k parts


For positive integers n and k, let a "k-partition of n" be a sorted list of k distinct positive integers that add up to n, and let the "rank" of a given k-partition of n be its position in the sorted list of all of these lists in lexicographic order, starting at 0.

For example, there are two 2-partitions of 5 (n = 5, k = 2): [1,4] and [2,3]. Since [1,4] comes before [2,3] in lexicographic order, the rank of [1,4] is 0 and the rank of [2,3] is 1.

So, I want to be able to do two things:

Can I do this without having to compute all the k-partitions of n that come before the one of interest?

This question is different from other because we are here talking about integer partitions and not just combinations.


Solution

  • Here is a solution in Python that relies on two ideas. First, dynamic programming to count partitions without generating them. Second, if the first value is i then we can look at this as an i * k box with a partition of n-i*k into k-1 pieces resting on top.

    partition_cache = {}
    def distinct_partition_count (n, k):
        if n < k:
            return 0
        elif n < 1:
            return 0
        elif k < 1:
            return 0
        elif k == 1:
            return 1
        elif (n, k) not in partition_cache:
            answer = 0
            for i in range(1, n//k + 1):
                answer = answer + distinct_partition_count(n - i*k, k-1)
            partition_cache[(n, k)] = answer
        return partition_cache[(n, k)]
    
    def rank_distinct_partition (values):
        values2 = sorted(values)
        n = sum(values)
        k = len(values)
        answer = 0
        highwater = 0
        for v in values:
            rise = v - highwater
            for i in range(1, rise):
                answer = answer + distinct_partition_count(n - k*i, k-1)
            highwater = v
            ## BUG HERE: was n = n - rise
            n = n - rise * k
            k = k - 1
        return answer
    
    def find_ranked_distinct_partition (n, k, rank):
        if k == 1 and rank == 0:
            return [n]
        elif distinct_partition_count(n, k) <= rank:
            return None
        elif rank < 0:
            return None
        else:
            i = 1
            while distinct_partition_count(n - i*k, k-1) <= rank:
                rank = rank - distinct_partition_count(n - i*k, k-1);
                i = i + 1
            answer = find_ranked_distinct_partition(n - i*k, k-1, rank)
            return [i] + [j + i for j in answer]
    
    print(rank_distinct_partition([2, 3]))
    print(find_ranked_distinct_partition(5, 2, 1))