如何创建一个不均匀区间数字随机函数?

3
我们知道经典的范围随机函数是这样的:

public static final int random(final int min, final int max) {
    Random rand = new Random();
    return min + rand.nextInt(max - min + 1);  // +1 for including the max
}

我想创建一个算法函数,用于在1到10的范围内随机生成数字,但是概率不均匀,如下所示:
1) 1,2,3 -> 3/6 (1/2)
2) 4,5,6,7 -> 1/6
3) 8,9,10 -> 2/6 (1/3)
上述意味着该函数有1/2的机会返回1到3之间的数字,1/6的机会返回4到7之间的数字,以及1/3的机会返回8到10之间的数字。
有人知道这个算法吗?
更新: 实际上,1到10的范围只是一个例子。我想创建的函数将适用于任何数字范围,例如:1到10000,但规则仍然相同:顶部范围(30%部分)为3/6,中间范围(接下来的40%部分)为1/6,底部范围(最后的30%部分)为2/6。

@suud:不同尺寸的范围会是什么样子? - Marcelo Cantos
例如,如果范围在1到10000之间(1,2,3,...,9998,9999,10000),那么1-3000的范围将有3/6的机会,3001-7000的范围将有1/6的机会,而7001-10000的范围将有2/6的机会。 - null
@MitchWheat 如果您认为这是一个重复的问题,下方有一个“关闭”链接,您可以在那里指定它是哪个问题的重复。如果您这样做,将为SO社区提供帮助。 - Nick Johnson
@MitchWheat 所以你基本上是在搞事情。 - Nick Johnson
我认为米奇是正确的。你可以用关键词“权重范围随机”找到它们,尽管规则与我的不同... - null
显示剩余2条评论
6个回答

5

使用这个算法:

int temp = random(0,5);
if (temp <= 2) {
  return random(1,3);
} else if (temp <= 3) {
 return random(4,7);
} else  {
 return random(8,10);
}

这应该能解决问题。
编辑:根据您的评论要求:
int first_lo = 1, first_hi = 3000; // 1/2 chance to choose a number in [first_lo, first_hi]
int second_lo = 3001, second_hi = 7000; // 1/6 chance to choose a number in [second_lo, second_hi] 
int third_lo = 7001, third_hi = 10000;// 1/3 chance to choose a number in [third_lo, third_hi] 
int second
int temp = random(0,5);
if (temp <= 2) {
  return random(first_lo,first_hi);
} else if (temp <= 3) {
 return random(second_lo,second_hi);
} else  {
 return random(third_lo,third_hi);
}

你好,如果范围在1到10000或其他范围数字之间怎么办? - null
请举一个例子,说明你的意思。 - Ivaylo Strandjev
你能否修改你的算法,使其可以接收任何输入数字作为最小值和最大值,而不是使用预定义的数字?这意味着该算法将会依赖于输入(希望这个术语是正确的,哈哈)。 - null
@sud 最小值和最大值将成为生成数字范围的限制,对吗?我想问的是如何传递每个数字的不均匀概率。请给出一个给定数字范围的示例输入。 - Ivaylo Strandjev
1
@suud 我已经更新了我的回答,尽可能地提供了针对所述问题的通用解决方案。如果您更好地指定输入,我可以尝试给出更接近您想要的更好的解决方案。 - Ivaylo Strandjev
显示剩余4条评论

3
您可以创建一个所需数字密度的数组,然后生成一个随机索引并获取相应的元素。我认为这样会更快一些,但可能不是很重要。下面是示例代码,仅供参考,不是正确解决方案:
1,1,1,2,2,2,3,3,3,4,5,6 ...

或者您可以先使用if语句定义域,然后从该域生成一个简单的数字。

int x = random(1,6)
if (x < 4) return random(1, 3);
if (x < 5) return random(4, 7);
return random(8, 10);

除了数组的密度不正确之外,其他都没问题。请参考Marcelo的答案获取正确的数组。 - Steve Jessop
是的,我知道。但这只是我所想的一个例子。 - Matzi

2

掷一颗72面骰子,从以下数组中选择:

// Each row represents 1/6 of the space
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7,
 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9,
 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10]

你好,能否再详细解释一下?我有点困惑。从你的数组中,我能理解到数字“1”出现的概率是12/72(1/6)。但是根据我的规则,数字“1”的概率应该是3/6(1/2)。 - null
2
@sud:我理解你的问题是希望从第一个集合中选择数字的概率为3/6。由于该集合有三个元素,因此选择该集合中任何给定数字的机会应该是其中的三分之一,即1/6。如果选择1的概率为1/2,选择2的概率也为1/2,选择3的概率同样为1/2,则从第一个集合中选择数字的概率将大于1,这是不合理的。 - Marcelo Cantos

0
final int lut[] = [
  1, 1, 1,
  2, 2, 2,
  3, 3, 3,
  4,
  5,
  6,
  7,
  8, 8,
  9, 9,
  10, 10
];

int uneven_random = lut[random.nextInt(lut.length)];

或类似的东西...


0

如果你使用整数,这可能会有所帮助:

你需要使用以下函数:

f : A -> B

B = [b0, b1] 表示您从随机数生成器中想要的值的范围

A = [b0,2 * b1] 以便 f 的最后一个分支实际上达到 b1

f(x) = step((x / 3) , 
    f(x) is part of interval [b0, length(B)/3]
f(x) = step(x)  + E,   
    f(x) is part of interval [length(B) / 3, 2 * length(B) / 3],
    E is a constant that makes sure the function is continuous
f(x) = step(x / 2) + F,   
    f(x) is part of interval [2 * length(B) / 3, length(B)]
    F is a constant that makes sure the function is continuous

解释:在第一个分支上获得相同值需要三倍的数字。因此,在第一个分支上获得数字的概率是第二个分支的三倍,其值从随机数生成器中均匀分布。第三个分支也是如此。
希望这可以帮到你!
编辑:修改了间隔,你需要微调一下,但这是我的一般想法。

你可能需要调整间隔。 - Cristian Toader

0

根据izomorphius的代码,以下是我编写的代码,希望能解决我的问题:

private static Random rand = new Random();

public static int rangeRandom(final int min, final int max) {
    return min + rand.nextInt(max - min + 1);  // +1 for including the max
}

/**
 * 
 * @param min           The minimum range number
 * @param max           The maximum range number
 * @param weights       Array containing distributed weight values. The sum of values must be 1.0 
 * @param chances       Array containing distributed chance values. The array length must be same with weights and the sum of values must be 1.0 
 * @return              Random number
 * @throws Exception    Probably should create own exception, but I use default Exception for simplicity
 */
public static int weightedRangeRandom(final int min, final int max, final float[] weights, final float[] chances) throws Exception {
    // some validations
    if (weights.length != chances.length) {
        throw new Exception("Length of weight & chance must be equal");
    }

    int len = weights.length;

    float sumWeight = 0, sumChance = 0;
    for (int i=0; i<len; ++i) {
        sumWeight += weights[i];
        sumChance += chances[i];
    }
    if (sumWeight != 1.0 || sumChance != 1.0) {
        throw new Exception("Sum of weight/chance must be 1.0");
    }

    // find the random number
    int tMin = min, tMax;
    int rangeLen = max - min + 1;

    double n = Math.random();
    float c = 0;
    for (int i=0; i<len; ++i) {
        if (i != (len-1)) {
            tMax = tMin + Math.round(weights[i] * rangeLen) - 1;
        }
        else {
            tMax = max;
        }

        c += chances[i];
        if (n < c) {
            return rangeRandom(tMin, tMax);
        }
        tMin = tMax + 1;
    }

    throw new Exception("You shouldn't end up here, something got to be wrong!");
}

使用示例:

int result = weightedRangeRandom(1, 10, new float[] {0.3f, 0.4f, 0.3f}, 
    new float[] {1f/2, 1f/6, 1f/3});

由于按权重分配子范围边界(tMin和tMax)的除法结果为小数值,因此代码可能仍存在不准确性。但我想这是不可避免的,因为范围数字是整数。

出于简单起见,可能需要一些输入验证,但我已经省略了。

欢迎批评、纠正和评论 :)


你好,能否解释一下为什么有两个浮点数数组,一个是权重,另一个是概率? - Aman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接