1

This is just a problem I thought of while I was in bed last night :) .

How do you split a number, let's call it N, in M random parts, so that each part has the same probability to be a number from 0 to N.

Eg: N= 5, M=3

The algorithm should return an array like one of these:

[0,2,3]   //0 + 2 +3 =5
[1,1,3]  
[0,0,5]  
[3,2,0]  
[2,2,1]  
...etc

The programming languange doesn't really matter.

XCS
  • 27,244
  • 26
  • 101
  • 151
  • Do you need to have all the numbers have an equal probability if you choose one from the matrix? i.e. if N=5 then matrix[x][y] has a 1/6 chance of returning a 1? – Asitaka Jan 29 '13 at 13:06
  • Yes. So let's say we run the algorithm 1M times. After all this runs we count how many times each number appears on each position in the array. The numbers should be ~equal for all numbers and all positions (0 appears 100 times on the first position, 1 appears 100 times on the second position, 4 appears 100 times on the first position, etc...) – XCS Jan 29 '13 at 13:10
  • Complexity of algo is important for you? – Толя Jan 29 '13 at 13:12
  • No... It's not homework or anything else, I just thought of this and I want to know a possible solution. Of course, optimal solutions are the best :) – XCS Jan 29 '13 at 13:25
  • I suspect that this isn't possible. With your `N=5; M=3` example, consider that the only way that 5 can appear is in some rearrangement of [0, 0, 5]. So right there, you've got 0 appearing at least twice as many times as 5. – Mark Dickinson Jan 29 '13 at 16:25
  • Actually in that case 0 appears once on the first position and once on the second position and 5 appears once in the third position. (it doesn't count as '0 appears twice' ) – XCS Jan 29 '13 at 16:48
  • Sure, but now add up across the positions. You want number of 0s in position 1 ~= number of 0s in position 2 ~= number of 0s in position 3 ~= number of 5s in position 1 ~= number of 5s in position 2 ~= number of 5s in position 3, right? So adding across the positions, you get total number of 5s ~= total number of 0s. And that's not going to happen. – Mark Dickinson Jan 29 '13 at 19:43

4 Answers4

2

I just subscribed to share my results about this particular problem. I've come to a solution that pleases me, even if, obviously, it's not a 100% random number split. But it fits my needs, and it is very light on resources.

I give you the code for the method (it's a recursive method) with comments on specific parts (others are pretty straight-forward I think). The code has been made in AS3, but the language is easy to read :

/**
 * This function splits a unique number into many numbers whose total equals to the one given as a parameter.
 * The function only works for size equals to a power of 2, but I think it can be easily done for any size,
 * by making as many "power of 2" calls as necessary to get a good final result.
 * @param   total The expected value of the sum of the resulting numbers
 * @param   minValue The minimum value each number can take
 * @param   maxValue The maximum value each number can take
 * @param   size The number of numbers we want to split the total into
 * @param   stepNum The step number of the recursive calls. Used to enhance the results of the algorithm.
 * @return
 */
    private function splitTotalInTwo(total:int, minValue:int, maxValue:int, size:int, stepNum:int):Vector.<int>
    {
        var result:Vector.<int> = new Vector.<int>();

        // we determine the min and max values allowed for the random generated number so that it doesn't 
        // make the second group impossible to process with success
        var minRand:int = Math.max(size * minValue, total - (size * maxValue));
        var maxRand:int = Math.min(size * maxValue, total - (size * minValue));

        // the balanceFactor is used to make the split tighter in the early stages of the recursive algorithm, 
        // therefore ensuring a best number distribution in the end. 
        // You can comment the next three lines to see the number distribution of th inital algorithm.
        // You can alsocchange the balancefactor to get which results you like most. 
        // This var good also be passed as parameter to fine tune the algorithm a little bit more.
        var balanceFactor:Number = 0.4;
        var delta:int = Math.floor((maxRand - minRand) * (0.4 / stepNum));
        minRand += delta;
        maxRand -= delta;

        var random:int = Math.floor(Math.random() * (maxRand - minRand)) + minRand;
        if (size > 1)
        {
            result = result.concat(splitTotalInTwo(random, minValue, maxValue, size / 2, stepNum+1), splitTotalInTwo(total - random, minValue, maxValue, size / 2, stepNum+1));
        }
        else 
        {
            result.push(random);
            result.push(total - random);
        }

        return result;
    }

Hope this helps...

ffourcad
  • 69
  • 4
1

Lets assume we have a method that generates a uniformly random distribution of numbers between [0,N]. An obvious way is to successively generate M random numbers in the range from [0,N] where we update N after each generation, since the sum of all generated numbers has to equal N. You would have to mathematically prove that this will result in a uniformly randomly distributed collection of pairs [x1,x2,....,xM]. I would say that this is not trivial. (For instance your example where the first number is randomly chosen to be 5, the following two numbers have to be zero as the sum can not exceed N=5 and therefore the randomnness is biased)

Another 'brute force' method that you might consider is generating a collection of all possible permutations [x1,x2,....,xM] where the sum x1+x2+...+xM = N. If the collection contains Y possible permutations you can then use our previously defined random generator to get a random number in the range [1,Y] and select that element from your collection

Note that this is just of the top of my head and if you want to ensure truely random uniform distributions you have to check these proposals mathematically.

Edit: I also just realized that probably, they way you described the problem, the order in which the number is split is irrelevant (i.e. [0,2,3] is the same as [2,3,0] is the same as [0,3,2]). This reduces the number of total unique permutation in the ensemble of Y groupings

Pankrates
  • 3,074
  • 1
  • 22
  • 28
  • Actually, couldn't you just make an array with all the unique permutations where x1+x2+...+xM = N and then randomize the rows in it so the numbers "appear" random? – Asitaka Jan 29 '13 at 13:15
  • You could, but the OP wants to select a single permutation, so if you generate the complete ensemble, you just need to pick one at random and the shuffling seems pointless to me – Pankrates Jan 29 '13 at 13:17
  • Hmm... But we should select SOME of the permutations from the total of Y so that each number appears on each position the same number of times, and then choose a random one from these. – XCS Jan 29 '13 at 13:28
0

You can try my approach. Initially I calculated amount of posible results and after choose random (implementation on C#):

class Program
{
    private static int[,] dp;

    private static void Populate(int m, int n)
    {
        dp[0, 0] = 1;
        for (int i = 1; i <= m; i++)
        {
            dp[i, 0] = 1;
            for (int j = 1; j <= n; j++)
            {
                dp[i, j] = dp[i - 1, j] + dp[i, j - 1];
            }
        }
    }

    private static int[] GetResultAt(int index, int m, int n)
    {
        int[] result = new int[m];

        while (m > 0)
        {
            for (int i = 0; i <= n; i++)
            {
                if (index <= dp[m - 1, i] && dp[m - 1, i] > 0)
                {
                    m--;
                    result[m] = n - i;
                    n = i;
                    break;
                }
                index = index - dp[m - 1, i];
            }
        }

        return result;
    }

    static void Main(string[] args)
    {
        int n = 5;
        int m = 3;
        dp = new int[m + 1, n + 1];
        Populate(m, n);

        int randomPosition = 7;// Random value from 1 and dp[m,n] range.
        int[] result = GetResultAt(randomPosition, m, n);

        Console.WriteLine(result);
    }
}

Define required m, n and randomPosition to get result. Complexity of this algo is O(M*N) for precalc and O(M+N) for get a random array.

Толя
  • 2,839
  • 15
  • 23
  • Isn't the populate complexity ~O(M*N^2) ? actually: O(M*N*(N+1)/2) ? Can you explain the algorithm and how does this achieve uniform distribution? – XCS Jan 29 '13 at 13:37
  • uniform distribution achieved because used random index or result. Distribution will be same as this number choose distribution, standard random use uniform distribution – Толя Jan 29 '13 at 15:12
0
public static Integer[] sliceUnequally(int value, int numberOfSlices) {
    if (numberOfSlices > value) {
        throw new IllegalArgumentException(
                String.format("!!! Cannot carve %d into %d slices", value, numberOfSlices));
    }

    final Integer[] slices = new Integer[numberOfSlices];

    int allocated = numberOfSlices;
    for (int i = 0; i < numberOfSlices; i++) {
        if (i == (numberOfSlices - 1)) {
            slices[i] = 1 + (value - allocated);
        } else {
            int slice = new Double((value - allocated) * random.nextDouble()).intValue();
            slices[i] = 1 + slice;
            allocated += slice;
        }
    }

    return slices;
}
DavidF
  • 1