将给定数量M随机分成N个部分

时间:2015-08-21 08:31:36

标签: java algorithm random sum

所以,我所拥有的想法是能够将2.00美元分成10个人,并且他们每个人将随机收到$ x.xx的金额。 (N和M将始终限制为2位小数且> 0)

Ex:{0.12,0.24,1.03,0.01,0.2,0.04,0.11,0.18,0.05,0.02}

目前我尝试过:

private static BigDecimal[] randSum(int n, double m)
{
    Random rand = new Random();
    BigDecimal randNums[] = new BigDecimal[n], sum = new BigDecimal(0).setScale(2);

    for (int i = 0; i < randNums.length; i++)
    {
        randNums[i] = new BigDecimal(rand.nextDouble()).setScale(2, RoundingMode.HALF_EVEN);
        sum = sum.add(randNums[i]);
    }

    for (int i = 0; i < randNums.length; i++)
    {
        BigDecimal temp1 = randNums[i].divide(sum, 2, RoundingMode.HALF_EVEN);
        BigDecimal temp2 = temp1.multiply(new BigDecimal(m).setScale(2));
        randNums[i] = temp2;
    }

    return randNums;
}

public static void main(String[] args)
{
    BigDecimal d[] = randSum(5, 2);

    double sum = 0;
    for (BigDecimal n : d)
    {
        sum += n.doubleValue();
        System.out.println(n);
    }
    System.out.println("total: " + sum);
}

但BigDecimals太混乱了,他们没有加起来。有时总数是1.98或2.01。由于Double-precision浮点,双打不起作用。

代码取自:

Getting N random numbers that the sum is M

4 个答案:

答案 0 :(得分:5)

假设您需要一个固定的精度(作为prec参数传递):

static public BigDecimal[] split(BigDecimal sum, int prec, int count) {
    int s = sum.scaleByPowerOfTen(prec).intValue();
    Random r = new Random();
    BigDecimal[] result = new BigDecimal[count];
    int[] v = new int[count];

    for (int i = 0; i < count - 1; i++)
       v[i] = r.nextInt(s);
    v[count - 1] = s;

    Arrays.sort(v);
    result[0] = BigDecimal.valueOf(v[0]).scaleByPowerOfTen(-prec);
    for (int i = 1; i < count; i++)
       result[i] = BigDecimal.valueOf(v[i] - v[i - 1]).scaleByPowerOfTen(-prec);
    return result;
}

此方法使用Random.nextInt()均匀分布的属性。排序后,v[]数组的值是分割整个金额的点,因此您可以使用相邻元素之间的差异生成结果:

[   2,    5,   10,   11, ...,  197,  200]  // v[]
[0.02, 0.03, 0.05, 0.01, ...,  ..., 0.03]  // result[]

这里使用整数值进行操作,因此舍入问题不再困扰。

答案 1 :(得分:2)

我建议将所有数字乘以100并重新解释您的问题:生成n随机非负整数,其总和等于给定的m整数。之后,您可以将生成的所有数字除以100,以获得所需内容。这是我的实现(类似于@SashaSalauyou版本):

private static int[] randSum(int n, int min, int m) {
    Random rand = new Random();
    int[] nums = new int[n];
    int max = m - min*n;
    if(max <= 0)
        throw new IllegalArgumentException();
    for(int i=1; i<nums.length; i++) {
        nums[i] = rand.nextInt(max);
    }
    Arrays.sort(nums, 1, nums.length);
    for(int i=1; i<nums.length; i++) {
        nums[i-1] = nums[i]-nums[i-1]+min;
    }
    nums[nums.length-1] = max-nums[nums.length-1]+min;
    return nums;
}

我还添加了一个参数min,这是最小号码。如果您在答案中接受零,请将其设置为0。否则,您可以将其设置为1(然后在100除以后,最低可能的数字为0.01。)

答案 2 :(得分:0)

您可以将此问题视为整数,而不是汇总到M,将其加到100M

做算法,你最终得到非整数,例如10.345
现在 - 基本上你想做的是取每个数字的最低值(10 in上面的例子),并将数字增加到11,概率与0.345成比例。

可以通过创建提醒数组来完成:rem[i] = value[i] - ceil(value[i]),并根据M - sum{ceil(value[i])}数组的加权概率选择带有替换的rem值。

代码:

public static BigDecimal[] createRandomSumsTo(BigDecimal M, int n) { 
    int m = M.multiply(BigDecimal.TEN).multiply(BigDecimal.TEN).intValue();
    double[] rands = new double[n];
    double sum = 0;
    for (int i = 0; i < n; i++) {
        rands[i] = rand.nextDouble();
        sum += rands[i];
    }
    for (int i = 0; i < n; i++) rands[i] = (rands[i] / sum) * m;
    int[] intVals = new int[n];
    double[] rem = new double[n];
    //create base and reminder array:
    for (int i =0 ; i < n; i++) { 
        intVals[i] = (int) Math.floor(rands[i]);
        rem[i] = rands[i] - intVals[i];
    }
    //for efficiently chosing a random value by weight
    double[] aux = new double[n+1];
    for (int i = 1 ; i < n+1; i++) { 
        aux[i] = aux[i-1] + rem[i-1]; 
    }
    //normalize to sum to one.
    for (int i = 0 ; i < n+1; i++) { 
        aux[i] = aux[i] / aux[n]; 
    }
    int intsSum = 0;
    for (int x : intVals) {
        intsSum += x;
    }
    for (; intsSum < m; intsSum++) { 
        intVals[chooseWeighted(aux)]++;
    }
    //and create the BigDecimal array:
    BigDecimal[] res = new BigDecimal[n];
    for (int i = 0; i < n; i++) { 
        res[i] = new BigDecimal(intVals[i]).divide(BigDecimal.TEN).divide(BigDecimal.TEN);
    }

    return res;

}

private static int chooseWeighted(double[] probabilities) {
    double r = rand.nextDouble();
    int idx = Arrays.binarySearch(probabilities, r);
    if (idx >= 0) return idx-1;
    return (-1*idx) -2;
}

答案 3 :(得分:0)

另一种可以调整生成数字“均匀度”的实现。

/**
 * Split a positive number into N random numbers.
 *
 * @param sum        the sum of the generated numbers
 * @param num        the number of numbers
 * @param min        the minimum number
 * @param multiplier a parameter used to adjust the evenness of the generated numbers. The farther it is away from 1
 *                   the more uneven the numbers are.
 * @param scale      the scale of the generated numbers
 * @return an array of BigDecimal objects
 */
public static BigDecimal[] split(double sum, int num, double min, double multiplier, int scale) {
    double leftover = sum - num * min;
    BigDecimal[] numbers = new BigDecimal[num];
    for (int i = 0; i < num - 1; i++) {
        double number = min;
        double max;
        if ((max = leftover / (num - i) * multiplier) <= leftover) {
            double rand = RandomUtils.nextDouble(0, max);
            number += rand;
            leftover -= rand;
        }
        numbers[i] = BigDecimal.valueOf(number).setScale(scale, RoundingMode.DOWN);
    }
    numbers[num - 1] = BigDecimal.valueOf(sum)
            .subtract(Arrays.stream(numbers).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add))
            .setScale(scale, RoundingMode.HALF_UP);
    ArrayUtils.shuffle(numbers);
    return numbers;
}