我无法根据概率生成随机字母。
例如,字母 J、K、Q、Y、Z 每个都有 1/96 的发生概率。类似的过程(具有更高的概率)用于其他字母。
有人可以告诉我怎么做吗?
编辑具体:我正在编写一个名为“getRandomLetter”的方法,它根据概率分数返回一个随机字母的字符。
我无法根据概率生成随机字母。
例如,字母 J、K、Q、Y、Z 每个都有 1/96 的发生概率。类似的过程(具有更高的概率)用于其他字母。
有人可以告诉我怎么做吗?
编辑具体:我正在编写一个名为“getRandomLetter”的方法,它根据概率分数返回一个随机字母的字符。
从具有特定概率的离散元素集合中进行选择的典型方法是选择一个随机浮点数并找出它所在的范围。我将用一个例子来解释。假设您在三个字母 A、B 和 C 中进行选择,概率分别为 0.255、0.407 和 0.338。您将计算一个介于 0 和 1 之间的随机数
double r = Math.random();
首先将其与 0 到 0.255 的范围进行比较:
if (r < 0.255) {
return 'A';
}
然后到 0.255 到 (0.255 + 0.407) 的范围:
else if (r < 0.662) {
return 'B';
}
如果不是其中任何一个,则必须是'C'
:
else {
return 'C';
}
如果你用字母表中的所有 26 个字母来做这件事,写出所有 26 个if
-else
语句的情况会很痛苦。您可以提前做的是准备一组字符及其各自的概率,
char[] chars = {'A', 'B', 'C', ...};
double[] probabilities = {0.01, 0.02, 0.05, ...};
然后你可以if
用这样的循环自动化所有这些:
double r = Math.random();
double cdf = 0.0;
for (int i = 0; i < chars.length; i++) {
cdf += probabilities[i]
if (r < cdf) {
return chars[i];
}
}
return chars[chars.length - 1];
在您的情况下,如果您的所有概率都是 1/96 的倍数,那么您可以选择小于 96 的随机整数而不是浮点数来做同样的事情。只需使用int
s 代替double
s,并使用rnd.nextInt(96)
选择 0 到 95 之间的整数,包括 0 和 95,而不是Math.random()
. 此外,您的probabilities
数组将包含实际概率乘以 96。
char[] chars = {'A', 'B', 'C', ...};
int[] probabilities = {5, 2, 4, ...}; // needs to sum to 96
// later...
int r = rnd.nextInt(96);
int cdf = 0;
for (int i = 0; i < chars.length; i++) {
cdf += probabilities[i]
if (r < cdf) {
return chars[i];
}
}
return chars[chars.length - 1];
现在,如果你正在做一些事情,比如从袋子里抽出拼字游戏牌,那么它会变得更棘手,因为这是一个没有替换的抽样过程,即每次抽奖后概率都会改变。我认为在这种情况下更好的方法是实际使用一个集合来模拟袋子,然后为每个上面有该字母的图块添加一个字母的副本。您仍然可以使用之前的相同chars
和probabilities
数组在循环中执行此操作:
char[] chars = {'A', 'B', 'C', ...};
int[] probabilities = {5, 2, 4, ...}; // number of tiles with each letter
LinkedList<Character> bag = new LinkedList<Character>();
for (int i = 0; i < chars.length; i++) {
for (int n = 0; n < probabilities[i]; n++) {
bag.add(chars[i]);
}
}
然后你可以bag.shuffle()
随机化瓷砖,bag.pop()
让你随机选择一个。
现在,假设您生成一个介于 0 和 95 之间的随机整数(96 种可能的变体)
然后,您可以将每个字母映射到其中一个数字。一个简单而肮脏的方法是使用 switch 语句
switch (randomNumber)
{
case 0:
//decide that you want J
break;
case 1:
case 2:
// maybe you want a letter to have a 2/96 probability
break;
}
另一种简单的方法是使用一个字符数组。
Random rand = new Random(new Date().getTime())
char[] charArray = {'A','B','C','C','D','E','F','F','F'};
char chosenChar = charArray[rand.nextInt(0, 96)];
你可以做的是这样的:
List<char> letters = new List<char>();
Dictionary<int,List<char>> set1 = new Dictionary<int,List<char>>();
set1.Key = 2;
set1.Value = new List<char>{'A','B'} //blah blah blah
制作这些字典的数组或列表并foreach它们
foreach (char theChar in set1.Value)
{
for (int i = 0; i < set1.Key;i++)
{
letters.add(theChar);
}
然后,
Random random = new Random();
char nextchar = letters[random.nextInt(letters.Count - 1)];
您希望它被选中的次数越多,您将其添加到列表中的次数就越多。
另外:如果需要,您可以用一种长度的字符串替换字符。
编辑:这是添加到字母的旧方法:
for (int i = 0; i < 4; i++) // 4 times
{
letters.add('a');
}
for (int i = 0; i < 3; i++) // 4 times
{
letters.add('b');
}
等等
Random r = new Random();
char c = (char) r.nextInt(25)+65;
最令人愉悦的解决方案将需要一个紧凑的容器来存储给定字母的出现概率。我建议使用 HashMap 作为概率函数(离散分布函数)。像这样:
HashMap<Character, Double> map = new HashMap<Character, Double>();
for(Character c : {'J', 'K', 'Q', 'Y', 'Z'}) {
map.put(c, 1.0 / 96.0);
}
// and so on
为了清楚起见,最好确保所有概率的总和等于1.0
,但这些数字可以被视为概率权重并在最后进行归一化。你明白了,对吧?
纯粹的数学方法需要创建一个累积分布函数,将其还原,然后明确使用该函数。这样,您可以提供一种生成具有几乎任何概率分布的任何随机值的解决方案。
让我们尝试一下:
double sum = 0.0, partialSum = 0.0;
HashMap<Double, Character> dist = new HashMap<Double, Character>();
for(Entry<Character, Double> entry : map.entrySet()) {
sum += entry.getValue(); // for normalization purpose, if you are really sure
// that all the probabilities sum up to 1.0, then the first loop is redundant
}
for(Map.Entry<Character, Double> entry : map.entrySet()) {
dist.put(partialSum / sum, entry.getKey());
partialSum += entry.getValue(); // the cumulative probability here
}
现在使用地图只需调用
Random r = new Random();
...
dist.get(r.nextDouble());