【问题标题】:Randomly split a given number M into N parts将给定数 M 随机拆分为 N 个部分
【发布时间】:2015-11-15 03:07:45
【问题描述】:

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

例如:{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。由于双精度浮点数,双精度数不起作用。

代码取自:

Getting N random numbers that the sum is M

【问题讨论】:

  • 如果m == 1.234 怎么办?你永远无法生成这样的数字。
  • 我已经修复了条件,N 和 M 将始终限制为 2 位小数和 > 0

标签: java algorithm random sum


【解决方案1】:

假设您需要一个固定的精度(作为 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[]

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

【讨论】:

    【解决方案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)。

    【讨论】:

    • 谢谢,这也是一个很好的解决方案,只使用整数,它会有更好的性能。但我会把功劳归功于@SashaSalauyou,因为他给出了第一个答案。我希望我能接受两者..
    【解决方案3】:

    您可以将此问题视为整数,而不是求和到 M,而是求和到 100M

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

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

    代码:

    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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      • 2013-01-13
      • 2013-09-29
      • 2014-09-11
      • 1970-01-01
      • 2018-10-28
      • 1970-01-01
      相关资源
      最近更新 更多