Is there an efficient way to generate a random combination of N integers such that—
min
, max
],
As the OP points out, the ability to efficiently unrank is very powerful. If we are able to do so, generating a uniform distribution of partitions can be done in three steps (restating what the OP has laid out in the question):
sum
such that the parts are in the range [min
, max
].[1, M]
.Below, we only focus on generating the nth partition as there is a copious amount of information on generating a uniform distribution of integer in a given range. Here is a simple C++
unranking algorithm which should be easy to translate to other languages (N.B. I have not figured out how to unrank the composition case yet (i.e. order matters)).
std::vector unRank(int n, int m, int myMax, int nth) {
std::vector z(m, 0);
int count = 0;
int j = 0;
for (int i = 0; i < z.size(); ++i) {
int temp = pCount(n - 1, m - 1, myMax);
for (int r = n - m, k = myMax - 1;
(count + temp) < nth && r > 0 && k; r -= m, --k) {
count += temp;
n = r;
myMax = k;
++j;
temp = pCount(n - 1, m - 1, myMax);
}
--m;
--n;
z[i] = j;
}
return z;
}
The workhorse pCount
function is given by:
int pCount(int n, int m, int myMax) {
if (myMax * m < n) return 0;
if (myMax * m == n) return 1;
if (m < 2) return m;
if (n < m) return 0;
if (n <= m + 1) return 1;
int niter = n / m;
int count = 0;
for (; niter--; n -= m, --myMax) {
count += pCount(n - 1, m - 1, myMax);
}
return count;
}
This function is based off of the excellent answer to Is there an efficient algorithm for integer partitioning with restricted number of parts? by user @m69_snarky_and_unwelcoming. The one given above is a slight modification of the simple algorithm (the one without memoization). This can easily be modified to incorporate memoization for greater efficiency. We will leave this off for now and focus on the unranking portion.
unRank
We first note there is a one-to-one mapping from the partitions of length N of the number sum
such that the parts are in the range [min
, max
] to the restricted partitions of length N of the number sum - N * (min - 1)
with parts in [1
, max - (min - 1)
].
As a small example, consider the partitions of 50
of length 4
such that the min = 10
and the max = 15
. This will have the same structure as the restricted partitions of 50 - 4 * (10 - 1) = 14
of length 4
with the maximum part equal to 15 - (10 - 1) = 6
.
10 10 15 15 --->> 1 1 6 6
10 11 14 15 --->> 1 2 5 6
10 12 13 15 --->> 1 3 4 6
10 12 14 14 --->> 1 3 5 5
10 13 13 14 --->> 1 4 4 5
11 11 13 15 --->> 2 2 4 6
11 11 14 14 --->> 2 2 5 5
11 12 12 15 --->> 2 3 3 6
11 12 13 14 --->> 2 3 4 5
11 13 13 13 --->> 2 4 4 4
12 12 12 14 --->> 3 3 3 5
12 12 13 13 --->> 3 3 4 4
With this in mind, in order to easily count, we could add a step 1a to translate the problem to the "unit" case if you will.
Now, we simply have a counting problem. As @m69 brilliantly displays, counting partitions can be easily achieved by breaking the problem into smaller problems. The function @m69 provides gets us 90% of the way, we just have to figure out what to do with the added restriction that there is a cap. This is where we get:
int pCount(int n, int m, int myMax) {
if (myMax * m < n) return 0;
if (myMax * m == n) return 1;
We also have to keep in mind that myMax
will decrease as we move along. This makes sense if we look at the 6th partition above:
2 2 4 6
In order to count the number of partitions from here on out, we must keep applying the translation to the "unit" case. This looks like:
1 1 3 5
Where as the step before, we had a max of 6
, now we only consider a max of 5
.
With this in mind, unranking the partition is no different than unranking a standard permutation or combination. We must be able to count the number of partitions in a given section. For example, to count the number of partitions that start with 10
above, all we do is remove the 10
in the first column:
10 10 15 15
10 11 14 15
10 12 13 15
10 12 14 14
10 13 13 14
10 15 15
11 14 15
12 13 15
12 14 14
13 13 14
Translate to the unit case:
1 6 6
2 5 6
3 4 6
3 5 5
4 4 5
and call pCount
:
pCount(13, 3, 6) = 5
Given a random integer to unrank, we continue calculating the number of partitions in smaller and smaller sections (as we did above) until we have filled our index vector.
Given min = 3
, max = 10
, n = 7
, and sum = 42
, here is an ideone demo that generates 20 random partitions. The output is below:
42: 3 3 6 7 7 8 8
123: 4 4 6 6 6 7 9
2: 3 3 3 4 9 10 10
125: 4 4 6 6 7 7 8
104: 4 4 4 6 6 8 10
74: 3 4 6 7 7 7 8
47: 3 4 4 5 6 10 10
146: 5 5 5 5 6 7 9
70: 3 4 6 6 6 7 10
134: 4 5 5 6 6 7 9
136: 4 5 5 6 7 7 8
81: 3 5 5 5 8 8 8
122: 4 4 6 6 6 6 10
112: 4 4 5 5 6 8 10
147: 5 5 5 5 6 8 8
142: 4 6 6 6 6 7 7
37: 3 3 6 6 6 9 9
67: 3 4 5 6 8 8 8
45: 3 4 4 4 8 9 10
44: 3 4 4 4 7 10 10
The lexicographical index is on the left and the unranked partition in on the right.