This sure is an interesting question (upvote!), and I look forward to seeing some more creative solutions. One issue I have with the code you've listed is that you're using the Random
class, which only returns integers, not longs, but then you just cast it to a long. So if your function really needs longs, you'll have to find a different random number generator.
The "ideal" solution would be to generate all possible additive combinations (called partitions) and then pick one randomly. However, the best-known algorithms for doing this are very expensive indeed. There's lots of good source material out there on this problem. Here's a particularly good read on partitioning algorithms:
http://www.site.uottawa.ca/~ivan/F49-int-part.pdf
Part of the problem is figuring out what you mean by "random" in this instance. For example, if you're looking for an array of 5 numbers that sum to 1000, {1000,0,0,0,0} is just as valid as {200,200,200,200,200}, which is just as valid as {136,238,124,66,436}. An algorithm that has an equal chance of generating any of these sets would be truly random.
However, I'm guessing that you're not looking for a completely random distribution, but something in between.
Unfortunately, my Java is pretty rusty, and I'm doing most everything in C# these days, so I'll present in C#...it shouldn't take too much translation to convert these algorithms into Java.
Here's my take at the problem:
public int[] GetRandoms( int size, int sum, Random r=null ) {
if( r==null ) r = new Random();
var ret = new int[size];
while( sum > 0 ) {
var n = r.Next( 1, (int)Math.Ceiling((double)sum/size)+1 );
ret[r.Next(size)] += n;
sum -= n;
}
return ret;
}
(On a practical note, see how I'm passing in my random number generator? I default it to null
and just create a new one for convenience's sake, but now I can pass in a seeded random number generator, and generate the same results if I wish, which is great for testing purposes. Whenever you have a method that utilizes a random number generator, I highly recommend taking this approach.)
This algorithm basically keeps adding random numbers to random entries in the array until the sum has been reached. Because of its additive nature, it will favor larger numbers (i.e., it will be extremely unlikely that small numbers will appear in the result). However, the numbers will appear to be random, and they will sum appropriately.
If your target number is small (under 100 say), you can actually achieve the true random approach of generating all of the partitions and just picking one at random. For example, here's a partitioning algorithm (courtesy http://homepages.ed.ac.uk/jkellehe/partitions.php, translated into C#):
public List<int[]> GetPartitions( int n ) {
var result = new List<int[]>();
var a = new int[n+1];
var k = 1;
a[0] = 0;
var y = n - 1;
while( k != 0 ) {
var x = a[k-1] + 1;
k--;
while( 2*x <= y ) {
a[k] = x;
y -= x;
k++;
}
var l = k + 1;
while( x <= y ) {
a[k] = x;
a[l] = y;
result.Add( a.Take( k + 2 ).ToArray() );
x++;
y--;
}
a[k] = x + y;
y = x + y - 1;
result.Add( a.Take( k + 1 ).ToArray() );
}
return result;
}
(To aid you in the Java translation, a.Take(x)
means take the first x
elements out of array a
...I'm sure there's some equivalent magic in Java these days for doing that.)
This returns a list of partitions...just pick one at random, and you'll have a perfect random distribution.