生成随机数,每个都是最小尺寸

时间:2012-01-13 07:19:34

标签: java algorithm random

不确定之前是否曾提出过这个问题,但问了类似的问题here。基本上,我正在尝试生成的最小大小的随机整数仍然总和到某个值(不变量是和/(你想要的randoms数)大于最小值。这是一个悲伤的尝试我编码了:

import java.util.Arrays;
import java.util.Random;

public class ScratchWork {

    private static Random rand = new Random();


    public static void main(String[] args) {
            int[] randoms = genRandoms(1000, 10, 30);
            for (int i = 0; i<randoms.length; i++) sop("Random Number "+ (i+1) + ": " + randoms[i]);
            sop("Sum: " + sum(randoms));
    }

    public static int sum(int[] array) { int sum = 0; for (int i : array) sum+=i; return sum; }

    public static int[] genRandoms(int n, int numberOfRandoms, int min) {
        if (min > n/numberOfRandoms) throw new UnsupportedOperationException();
        int[] intRandArray = {0};
        while (sum(intRandArray) != n) {
            ////////////////////////
            // See https://stackoverflow.com/questions/2640053/getting-n-random-numbers-that-the-sum-is-m
            Double[] randArray = new Double[numberOfRandoms];
            double workerSum = 0;
            for (int i = 0; i<numberOfRandoms; i++) {
                randArray[i] = ((double)rand.nextInt(n-1)+1);
                workerSum += randArray[i];
            }

            for (int i = 0; i<randArray.length; i++) {
                randArray[i] = n*(randArray[i]/(workerSum));
            }
            /////////////////////////

            while (existsSmallerNumThanMin(randArray, min)) 
                randArray = continueToFix(randArray, min);

            //Convert doubles to ints
            intRandArray = new int [randArray.length];
            for (int i = 0; i < intRandArray.length; i++) 
                intRandArray[i] = (int)Math.round(randArray[i]);
        }
        return intRandArray;
    }

    public static boolean existsSmallerNumThanMin(Double[] randArray, int min) {
        for (double i : randArray) if (i < (double)min) return true;
        return false;
    }

    public static Double[] continueToFix(Double[]randArray, int min) {
        Double[] tempArray = Arrays.copyOf(randArray, randArray.length);
        Arrays.sort(tempArray);
        int smallest = Arrays.asList(randArray).indexOf(tempArray[0]);
        int largest = Arrays.asList(randArray).indexOf(tempArray[tempArray.length-1]);

        double randomDelta = rand.nextInt(min);

        randArray[smallest]+=randomDelta;
        randArray[largest]-=randomDelta;
        return randArray;
    }

    public static void sop(Object s) { System.out.println(s); }

}

这既不是一种优雅也不是高效的方式......当传入时,它似乎也不能很好地工作(如果有的话),比方说,(100,10,10),它只允许对于数组中的数字10。随机数的分布也很糟糕。

这是一种优雅的方法吗?

此外,我的最终目标是在Objective-C中实现这一点,尽管我仍然只是学习该语言的绳索,因此非常感谢使用该语言执行此操作的任何提示。

5 个答案:

答案 0 :(得分:5)

我做了一些测试,我的Java实现被破坏了。我认为我的算法很糟糕。

如何在[0,1]中获取N个随机数并根据其总和(不同于所需总和)对结果进行标准化。然后将这些值乘以所需的数字和。

这假设最小大小为0.概括这一点以保持每个结果的最小值很简单。只需为sum - min * n提供sum。将min添加到所有结果中。

为了避免潜在的浮点问题,您可以使用逻辑上在[0,M](M任意大)M而不是[0,1]上的一系列整数值来执行类似操作。这个想法是相同的,因为你“标准化”你的随机结果。因此,假设你得到N的随机和(希望N必须是sum+1的倍数...请参阅REMARK)。 Divy N上升sum+1部分。第一部分是0,第二部分是1,......,最后是sum。每个随机值通过查看它所属的N的哪个部分来查找其值。

备注:如果您对任意少量的偏差感到满意,请使每个掷骰子的幅度大于sum(可能需要BigInteger)。设N为总和。然后N = k*sum + rem rem < sum。如果N很大,那么rem / k -> 0,这是好的。如果M很大,则N在概率上很大。

算法伪代码:

F(S, N):
    let R[] = list of 0's of size N
    if S = 0: return R[]
    for 0 <= i < N
        R[i] = random integer in [0, M] -- inclusive and (M >= S). M should be an order larger than S. A good pick might be M = 1000*S*N. Not sure though. M should probably be based on both S and N though or simply an outragously large number.
    let Z = sum R[]
    for 0 <= i < N
        R[i] = (Z mod R[i]) mod (S+1)
    return R[]

Java实现:

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;


public class Rand {
    private final Random rand;

    public Rand () {
        this(new Random());
    }

    public Rand (Random rand) {
        this.rand = rand;
    }

    public int[] randNums (int total, int minVal, int numRands) {
        if (minVal * numRands > total) {
            throw new IllegalArgumentException();
        }
        int[] results = randNums(total - minVal * numRands, numRands);
        for (int i = 0; i < numRands; ++i) {
            results[i] += minVal;
        }
        return results;
    }

    private int[] randNums (int total, int n) {
        final int[] results = new int[n];
        if (total == 0) {
            Arrays.fill(results, 0);
            return results;
        }
        final BigInteger[] rs = new BigInteger[n];
        final BigInteger totalPlus1 = BigInteger.valueOf(total + 1L);
        while (true) {
            for (int i = 0; i < n; ++i) {
                rs[i] = new BigInteger(256, rand);
            }
            BigInteger sum = BigInteger.ZERO;
            for (BigInteger r : rs) {
                sum = sum.add(r);
            }
            if (sum.compareTo(BigInteger.ZERO) == 0) {
                continue;
            }
            for (int i = 0; i < n; ++i) {
                results[i] = sum.mod(rs[i]).mod(totalPlus1).intValue();
            }
            return results;
        }
    }

    public static void main (String[] args) {
        Rand rand = new Rand();
        Map<Integer, Integer> distribution = new TreeMap<Integer, Integer>();
        final int total = 107;
        final int minVal = 3;
        final int n = 23;
        for (int i = total - (n-1) * minVal; i >= minVal; --i) {
            distribution.put(i, 0);
        }
        for (int i = 0; i < 100000; ++i) {
            int[] rs = rand.randNums(total, minVal, n);
            for (int r : rs) {
                int count = distribution.get(r);
                distribution.put(r, count + 1);
            }
        }
        System.out.print(distribution);
    }

}

答案 1 :(得分:3)

这可以满足您的需求吗?

import java.util.Arrays;
import java.util.Random;

public class ScratchWork {
    static Random rng = new Random();

    public static int randomRange(int n) {
        // Random integer between 0 and n-1
        assert n > 0;
        int r = rng.nextInt();
        if(r >= 0 && r < Integer.MAX_VALUE / n * n) {
            return r % n;
        }
        return randomRange(n);
    }

    public static int[] genRandoms(int n, int numberOfRandoms, int min) {
        int randomArray[] = new int[numberOfRandoms];
        for(int i = 0; i < numberOfRandoms; i++) {
            randomArray[i] = min;
        }
        for(int i = min*numberOfRandoms; i < n; i++) {
            randomArray[randomRange(numberOfRandoms)] += 1;
        }
        return randomArray;
    }

    public static void main(String[] args) {
        int randoms[] = genRandoms(1000, 10, 30);
        System.out.println(Arrays.toString(randoms));

    }
}

答案 2 :(得分:2)

根据以下评论中的修订要求。

public static int[] genRandoms(int total, int numberOfRandoms, int minimumValue) {
    int[] ret = new int[numberOfRandoms];
    Random rnd = new Random();
    int totalLeft = total;
    for (int i = 0; i < numberOfRandoms; i++) {
        final int rollsLeft = numberOfRandoms - i;
        int thisMax = totalLeft - (rollsLeft - 1) * minimumValue;
        int thisMin = Math.max(minimumValue, totalLeft / rollsLeft);
        int range = thisMax - thisMin;
        if (range < 0)
            throw new IllegalArgumentException("Cannot have " + minimumValue + " * " + numberOfRandoms + " < " + total);
        int rndValue = range;
        for (int j = 0; j * j < rollsLeft; j++)
            rndValue = rnd.nextInt(rndValue + 1);
        totalLeft -= ret[i] = rndValue + thisMin;
    }
    Collections.shuffle(Arrays.asList(ret), rnd);
    return ret;
}

public static void main(String... args) throws IOException {
    for (int i = 100; i <= 1000; i += 150)
        System.out.println("genRandoms(" + i + ", 30, 10) = " + Arrays.toString(genRandoms(1000, 30, 10)));
}

打印

genRandoms(100, 30, 10) = [34, 13, 22, 20, 26, 12, 30, 45, 22, 35, 108, 20, 31, 53, 11, 35, 20, 11, 18, 32, 14, 30, 20, 19, 31, 31, 151, 45, 25, 36]
genRandoms(250, 30, 10) = [30, 27, 25, 34, 28, 33, 34, 82, 45, 30, 24, 26, 26, 45, 19, 18, 95, 28, 22, 30, 30, 25, 38, 11, 18, 27, 77, 26, 26, 21]
genRandoms(400, 30, 10) = [48, 25, 19, 22, 36, 65, 24, 29, 49, 24, 11, 30, 33, 41, 37, 33, 29, 36, 28, 24, 32, 12, 28, 29, 29, 34, 35, 28, 27, 103]
genRandoms(550, 30, 10) = [25, 44, 72, 36, 55, 41, 11, 33, 20, 21, 33, 19, 29, 30, 13, 39, 54, 26, 33, 30, 40, 32, 21, 31, 61, 13, 16, 51, 37, 34]
genRandoms(700, 30, 10) = [22, 13, 24, 26, 23, 61, 44, 79, 69, 25, 29, 83, 29, 35, 25, 13, 33, 32, 13, 12, 30, 26, 28, 26, 14, 21, 26, 13, 84, 42]
genRandoms(850, 30, 10) = [11, 119, 69, 14, 39, 62, 51, 52, 34, 16, 12, 17, 28, 25, 17, 31, 32, 30, 34, 12, 12, 38, 11, 32, 25, 16, 31, 82, 18, 30]
genRandoms(1000, 30, 10) = [25, 46, 59, 48, 36, 32, 29, 12, 27, 34, 33, 14, 12, 30, 29, 31, 25, 16, 34, 44, 25, 50, 60, 32, 42, 32, 13, 41, 51, 38]

恕我直言,改组结果可以改善分布的随机性。

答案 3 :(得分:1)

也许是一种递归方式:

  • 如果numberOfRandoms为1,则返回n
  • 如果numberOfRandoms是x + 1
    • myNumbermin之间获得一个随机整数(n-min*x)(这样,剩下的min个数字至少留下x
    • 获取加起来为x
    • 的剩余n-myNumber

答案 4 :(得分:0)

这是一种生成固定长度随机数的实用方法。

    public final static String createRandomNumber(long len) {
    if (len > 18)
        throw new IllegalStateException("To many digits");
    long tLen = (long) Math.pow(10, len - 1) * 9;

    long number = (long) (Math.random() * tLen) + (long) Math.pow(10, len - 1) * 1;

    String tVal = number + "";
    if (tVal.length() != len) {
        throw new IllegalStateException("The random number '" + tVal + "' is not '" + len + "' digits");
    }
    return tVal;
}