Resurrecting an old question because I think we should start with:
Needs["Combinatorica`"]
RandomPartition[28]
RandomComposition[28, 5]
The RandomPartition
command seems to do what Artes was doing (except not limited to 5 summands, more on that below). The RandomComposition
seems to do what others were trying to do.
Although I'm not finding the right documentation to verify, I assume these choose a partition (composition) uniformly at random from all possible partitions (compositions) of 28 (into 5 parts, for the composition). I'm not sure if this was a feature that we didn't have at the time this question was asked (Artes didn't mention, so perhaps not, I'm not seeing dates in documentation). Maybe RandomPartition
was just passed over because it doesn't allow us to specify the length of the partition?
So I will grant that the question is vague, but clearly, we should be able to improve Artes answer using a built-in command like RandomPartition
if that command is written well at all. In principle, we should be able to do this faster than something that requires generating all possible partitions. And if the goal was a composition, RandomComposition
seems to take care of that out-of-the-box.
Now, if you just need it to be exactly five, and you don't care about increasing the value 28 or 5, there is a crude way of doing it, similar to the original code:
x = {}; While[Length[x] != 5, x = RandomPartition[28]]; x
That will also be uniform (if you want a random integer 1 to 5, just roll a 6-sided die and throw away the 6s -- it's still uniform).
Before we go any further, I'll just define "partition" and "part." A partition of $n$ is a set of positive integers that add up to $n$ (taken "in no particular order", so $5=1+1+3$ is the same as $5=3+1+1$). Each of the summands is a part, so $5=3+1+1$ is a partition of 5 into 3 parts.
If you want, for example, a partition of 100 into 5 parts (or into 90 parts), that While
loop takes much longer (on expectation) because there are relatively few partitions of 100 into exactly five (or 90) parts.
So let's talk a bit about efficiency. Say you want $n$ (e.g. $n=28$) to be partitioned randomly into $k$ (e.g. $k=5$) parts. It's been a while and I don't remember precisely, but for relatively feasible values of $n$, if $k$ is something like $\sqrt n$, you should be fine using the While
loop.
Note: For small values of $n$, or relatively few computations, this doesn't take too long. So if you just want to do this for $n=28$ or something, you can stop here and you're fine. The While
loop is fine -- use that. Or use Artes's solution. (Or if you're looking for slightly different type of output, use RandomComposition
or perhaps Jacob Akkerboom's answer.) The speed is negligible at such low values, comparing thousandths of seconds to ten-thousandths.
For our own use, let's piece together a function to count the number of partitions of $n$ into $k$ parts:
myP[n_, k_] := myP[n, k] =
If[n <= 0 || k <= 0||k>n, 0,
If[k == 1 || k == n, 1,
myP[n - 1, k - 1] + myP[n - k, k]
]
];
We'll use that shortly to help us balance out our probabilities, assuming uniformity is still the goal. This is built up on a recurrence $p(n,k)=p(n-1,k-1)+p(n-k,k)$. Any partition of $n$ into $k$ parts ends in 1, in which case you can remove it and obtain a partition of $n-1$ into $k-1$ parts, or else you can remove 1 from each part, obtaining a partition of $p-k$ into $k$ parts. It's not hard to prove this is bijective, so the recurrence is exact.
One thing we're going to capitalize on is this idea of removing 1 from each part.
If $k$ is significantly smaller or larger than this nice middle ground (which is probably not exactly $\sqrt n$, but off the top of my head, that's the best estimate you'll get out of me), you'll want to be more careful.
First, say $k$ is very large. Many of the parts should be small. For example, if $n=101$ and $k=58$, many of the parts have to be 1s (in fact, 15 of them do, because $15\times 1 + 43\times 2 = 101$). So a random partition of 100 into 58 parts is likely to have 1s we want to ignore.
If we could ignore the 1s, we could subdivide the problem by randomly generating the (hopefully much smaller) remaining parts randomly. The issue is that there could be more than 15 of them that are 1. If it were always exactly 15, this would be easier, but in fact, k-1 of them could be 1.
But if we could remove all the 1s, we could take all those parts of 2 or more, remove 1 from them, and repeat this process. Remember, we want to do it randomly! And we want to be careful to weight things so that we don't have some issue with a non-uniform sampling.
Say we have $n=101$ and $k=58$. There are at least 15 parts that are 1, as we mentioned, but there could be more. If $k_1$ of them are 1s, the remaining $k-k_1$ make up a partition of $n-k_1$. We break that down into a new, smaller partition.
For example, say we have a smaller partition like:
$$3+2+2+2+1+1+1+1+1+1+1+1+1+1+1+1+1+1.$$
We split off the parts that are 1:
$$3+2+2+2((+1+1+1+1+1+1+1+1+1+1+1+1+1+1)).$$
We discard those to obtain:
$$3+2+2+2,$$
a partition in which no part is 1.
We can make this correspond to the same partition with every part decreased by 1 (since we got rid of the 1s):
$$2+1+1+1.$$
If we could randomly generate such a partition, we can use this to randomly generate our original partition, except that we need to bias the sample. We can't choose this parameter $k_1$ uniformly (indeed, $k_1$ can't be less than 15 for sure, but even so, it's not uniform among feasible choices).
We need to know the probability of each possible value of $k_1$ (which could be anything, in general, from none, all the way to $k_1$, although as we noticed, there can be some restrictions making $k_1$ large -- which is good for us, to have $k_1$ bigger).
But we know how to count this: There are $p(101-k,58-k_1)$ such partitions to build upon, and each of those will correspond with one of our possible random partitions. So we can tally up this list by making a table:
myCount=Table[myP[101-k,58-j],{j,0,57}]
We need to choose according to these, and we need them to be probabilities, so we normalize to 1:
myProb=N[Normalize[myCount, Total]]
We can use that to randomly sample values (using RandomChoice
) of $k_1$. From there we can recursively build our partition that has high $k$ value.
To pack that up as a Module, we can do:
normalK[n_, k_] := Module[{part = {}},
While[Length[part] != k, part = RandomPartition[n]];
Return[part]
]
highK[n_, k_] := Module[{k1, prob, subpart = {}},
If[k < 6 Sqrt[n], Return[normalK[n, k]]];
prob = Normalize[Table[myP[n - k, k - j], {j, 0, k - 1}], Total];
k1 = RandomChoice[prob -> Range[0, k - 1]];
If[k - k1 < 6 Sqrt[n - k1] && n - k1 > 20,
subpart = 1 + normalK[n - k, k - k1],
subpart = 1 + highK[n - k, k - k1]
];
Return[Join[subpart, Table[1, {k1}]]];
];
That's the best I can come up with for $k$ relatively large. We can test it for speed, but I think we are optimistic already:
AbsoluteTiming[Do[normalK[100, 60];, {10}]]
(* Output: {44.2695, Null} *)
AbsoluteTiming[Do[highK[100, 60];, {10}]]
(* Output: {0.185296, Null} *)
It's way faster.
If $k$ is very small instead and the While
loop runs too slowly, you'll have to sort of do the "opposite" -- write a similar module you could call lowK
that looks at the largest part of the partition. If you know more about partitions, I'm suggesting essentially that lowK
operates on the "conjugate" of the ideas in highK
. Having one very large part is similar to having many 1s based on this concept, see: https://en.wikipedia.org/wiki/Partition_(number_theory)#Conjugate_and_self-conjugate_partitions
Ideally, you'd combine these two modules so that it's a single recursive module that can handle low, high, or normal ranges of k
based on each of these ideas, but I'll leave those details to anyone who's come this far in my answer and wants to implement it.
So now does this stack up against other answers or other perspectives? Well, clearly, if order matters (or doesn't matter, depending on what "matters" means to you), this will not work -- and randomly permuting it will hurt our carefully balanced uniformity. It may be possible to sneak that into the RandomChoice
probability distribution in highK
, but at that point we'd also need to work up our own normalK
too, since we wouldn't be able to rely on RandomPartition
at all. But we've already seen RandomComposition
, and Jacob Akkerboom gives one way to do it, coming from a different perspective.
If we are happy with the ordering but want 0s, it's quite easy to do one of:
normalK[n+k,k]-1
highK[n+k,k]-1
And just to prove that this wasn't a completely vacuous exercise, let's compare the timing:
AbsoluteTiming[Do[highK[100, 60];, {100}]]
(* Output: {0.74234, Null} *)
AbsoluteTiming[Do[RandomChoice[IntegerPartitions[100, {60}]];, {100}]]
(* Output: {3.28749, Null} *)
multDistr = MultinomialDistribution[100, ConstantArray[1/60, 60]];
AbsoluteTiming[Do[Reverse[Sort[RandomVariate[multDistr]]];, {100}]]
(* Output: {0.653411, Null} *)
AbsoluteTiming[Do[RandomComposition[100, 60], {100}];]
(* Output: {0.0142456, Null} *)
AbsoluteTiming[Do[RandomPartition[100], {100}];]
(* Output: {0.113123, Null} *)
So of course we're quite faster than normalK
, as shown above, but also faster than Artes' method (saving some time not constructing all the possible partitions) and surprisingly competitive with Jacob Akkerboom's method (which surprised me). But RandomComposition
trumps Jacob's algorithm, as we would expect, and it performs better than our highK
(I certainly woudln't expect highK
to be better, but it's at least comparable).
The Reverse
and Sort
are to make the output "the same" in some sense -- but they add negligible time anyway. And I'm letting Artes's code produce the table of values every single time because I want a good head-to-head comparison if, for example, you wanted to use it repeatedly with $n$ and $k$ of a consistent size. If you are literally using the exact same $n,k$ values over and over and Artes's method is feasible for those values, it may be cheaper to generate the table once and then make multiple random choices from it. But that's not my assumption.
If we increase $k$ a bit (e.g. 80), Jacob's method ends up being slower, and Artes's is faster. That makes sense because the list is very short. But if we increase $n$ a bit too (e.g. 400, with $k=320$), highK
is still about as fast as Jacob's method, and Artes's seems infeasible.
If we increase $n$ substantially, we continue to perform well:
AbsoluteTiming[Do[highK[1000, 900];, {10}]]
(* Out= {0.348483, Null} *)
multDistr2 = MultinomialDistribution[1000, ConstantArray[1/900, 900]];
AbsoluteTiming[Do[Reverse[Sort[RandomVariate[multDistr2]]];, {10}]]
(* Out= {1.01725, Null} *)
AbsoluteTiming[Do[RandomComposition[1000, 900], {100}];]
(* Output: {0.0224496, Null} *)
AbsoluteTiming[Do[RandomPartition[1000], {100}];]
(* Output: {3.29139, Null} *)
I recommend no one attempt Artes's solution for $n=1000$. It does not scale well, but of course, I think everyone understood that was not the intention. Artes's solution would have provided a way to do highK
(and lowK
) but in a way that won't necessarily scale very well. But RandomPartition
does, and so does our highK
(and our theoretical lowK
and whatever we build to tie all three together).
And there's a big surprise -- we're now beating RandomPartition
even though we're doing more work (abstractly) than it.
If we fix $n-k=100$ and increase $n$ it seems like Jacob's timing increases (linearly?), as does RandomComposition
at its much slower values, but ours doesn't seem to change at all. Of course, those two do something very different than our highK
.
Anyway, the TLDR is that I've suggested using RandomComposition
(if you want a composition, which seems to be the general consensus or at least plurality of interpretation), and that RandomPartition
might be useful if you want a partition like in Artes's popular, but non-scalable, answer. These two Combinatorica
functions are nice aesthetically, for low $n,k$, because they use built-in functionality, and they are very practical for higher $n,k$. And because we still don't get everything out of the box, I've whipped up a way of making RandomPartition
work when what we really want is a nonexistent version that accepts a second argument, like RandomPartition[28,{5}]
.
Total[x] == 28
? I mean, they are not independent. p.s. Not efficient -RandomChoice @ IntegerPartitions[28, {5}]
$\endgroup$While[Total@x=!=28,x = RandomInteger[{0, 28}, 5]];Print[x];
(note the print now outside the loop) $\endgroup$