如何生成一个范围内的随机数但排除一些随机数,而不继续生成并检查生成的数字是否是我想要排除的数字之一?
无需每次重新生成随机数的一种可能的解决方案是使用以下算法:
public int getRandomWithExclusion(Random rnd, int start, int end, int... exclude) {
int random = start + rnd.nextInt(end - start + 1 - exclude.length);
for (int ex : exclude) {
if (random < ex) {
break;
}
random++;
}
return random;
}
可以使用数组引用调用此方法,例如
int[] ex = { 2, 5, 6 };
val = getRandomWithExclusion(rnd, 1, 10, ex)
或者直接在通话中插入号码:
val = getRandomWithExclusion(rnd, 1, 10, 2, 5, 6)
它会生成一个介于
start
和 end
(两者都包含)之间的随机数 (int),并且不会为您提供数组 exclude
中包含的任何数字。所有其他数字以相同的概率出现。请注意,必须满足以下约束: exclude
按升序排序,并且所有数字都在提供的范围内,并且所有数字都互不相同。
/**
* @param start start of range (inclusive)
* @param end end of range (exclusive)
* @param excludes numbers to exclude (= numbers you do not want)
* @return the random number within start-end but not one of excludes
*/
public static int nextIntInRangeButExclude(int start, int end, int... excludes){
int rangeLength = end - start - excludes.length;
int randomInt = RANDOM.nextInt(rangeLength) + start;
for(int i = 0; i < excludes.length; i++) {
if(excludes[i] > randomInt) {
return randomInt;
}
randomInt++;
}
return randomInt;
}
这个想法是将生成随机数的范围缩小为开始和结束之间的差值减去该范围内被排除的数字的数量。
因此您得到的范围长度与可能的有效数字的数量相同。换句话说:您已将所有孔从范围中移除。
生成随机数后,您必须将“洞”放回范围内。只要排除小于或等于生成的数字,就可以通过增加生成的数字来实现这一点。较低的排除数字是生成数字之前的范围中的“空洞”。对于该数字之前的每个洞,生成的数字都会向右移动。
您可以遵循的随机数字的最佳方法(排除某些方法)是首先选择您想要的数字,然后随机选择所选的数字。例如,在伪代码中:
List<Number> numbers;
numbers.add(1);
numbers.add(2);
numbers.add(3);
//You can do a "for" without adding the excluded numbers..
//Then, your randomizer could be...
public Number getRandoNumber() {
int index = Random.get(0, numbers.size());
return numbers.get(index);
}
现在,您不需要检查“生成的数字”是否允许,因为它根本不存在。
如果您不想让它们重复,您可以执行以下操作:
Collections.shuffle(numbers);
public Number getRandomNotRepeat() {
if(numbers.size() == 0)
throw new RuntimeException("No more numbers");
Number n = numbers.get(0);
numbers.removeFirst();
return n;
}
创建一个映射,获取没有范围限制的随机函数的输出,并将其映射到您想要的有限制的范围。
例如,如果我想要一个 1-10 之间的随机整数,但绝不是 7,我可以这样做:
int i = rand(1, 9);
if i>=7
i++;
return i;
只要确保映射为 1:1,就可以避免扭曲
rand
函数的随机性。
这是最可靠的方法。
使用您的范围创建一个数组,删除所有排除元素
选择一个随机索引并返回该位置的值。
public int getRandomNumber(int start, int end, List<Integer> excludingNumbers) {
int[] range = IntStream.rangeClosed(start, end).toArray();
List<Integer> rangeExcluding = Arrays.stream( range ).boxed().collect( Collectors.toList() );
rangeExcluding.removeAll(list);
int newRandomInt = new Random().nextInt(rangeExcluding.size())
return rangeExcluding.get(newRandomInt);
}
范围包括开始和结束
我认为额外的问题是:你想排除哪些数字?它们代表某种“范围”还是“完全随机”?
如果您想忽略一系列数字,您可以从仅代表有效数字的几组中生成随机数:
rand(1,9);
rand(15,19);
rand(22,26);
这样您就可以确定您永远不会选择排除:
27然后,当您获得 3 个号码时,您可以再次随机选择其中一个。
<0,10,11,12,13,14,20,21,>如果排除的数字遍布各处,恐怕您每次都必须对照某种排除的数字集合进行检查。
排除应在参数范围内的数字
示例:
val random = arrayOf((-5..-1).random(), (1..10).random()).random()
这是我想出的解决方案。一种利用“无限”整数流和过滤的静态实用方法:
如果您确实想对其进行超级优化,您可以使用
int[]
数组而不是
Set<Integer>
来包含要排除的整数以避免装箱。
我也这样做了,所以上面的示例中包含了最大值,但只要不在max
值上加 1 就会使其不包含在内。
.limit(Long.MAX_VALUE)
是为了避免永远不返回值也不抛出异常的无限流。由于我经常只使用此方法作为内部实用程序,因此我通常会省略这部分,但拥有它更安全。
根据您排除的随机数列表有多大,我将生成您的数字并检查它是否在排除的数字数组中 - 如果是,则将其丢弃。我知道您不想每次都检查,但除了明确指定范围之外,我想不出其他方法,如果您排除的数字超过 5 个,情况可能会更糟。
可以工作并适用于 int 和 double 数字的东西可能是这样的:
问候, 史蒂芬