我假设 OP 希望大约 60% 的生成值为零,剩余的大约 40% 是 1-100 范围内的(伪)随机值,包括 1-100。
JDK 库可以轻松生成包含 N 个不同值的流。
由于 [1,100] 范围内有 100 个值,这代表输出的 40%,因此需要有 150 个映射到零的值以覆盖剩余的 60%。因此 N 为 250。
我们可以创建一个范围为 [0,249](含)的整数流,并将此范围内的最低 150 个值映射为零,将其余值保留在 [1,100] 范围内。代码如下:
IntStream is = random.ints(0, 250)
.map(i -> Math.max(i-149, 0));
更新
如果任务是准确地产生 60% 的零,有一种方法可以做到这一点,使用 Knuth、TAOCP 第 2 卷、第 3.4.2 节、随机采样和洗牌中的算法变体, 算法 S。(我在 this other answer 中更详细地解释了这个算法。)这个算法让人们从 N 个元素的集合中随机选择 n 个元素,对集合进行一次遍历。
在这种情况下,我们不是从集合中选择元素。相反,我们发出已知数量的数字,要求其中的一些子集为零,其余部分是某个范围内的随机数。基本思想是,当您发出数字时,发出零的概率取决于剩余要发射的零的数量与剩余的要发射的数字的数量。由于这是一个固定大小的流,并且它有一些状态,我选择使用 Spliterator 来实现它:
static IntStream randomWithPercentZero(int count, double pctZero, int range) {
return StreamSupport.intStream(
new Spliterators.AbstractIntSpliterator(count, Spliterator.SIZED) {
int remainingInts = count;
int remainingZeroes = (int)Math.round(count * pctZero);
Random random = new Random();
@Override
public boolean tryAdvance(IntConsumer action) {
if (remainingInts == 0)
return false;
if (random.nextDouble() < (double)remainingZeroes / remainingInts--) {
remainingZeroes--;
action.accept(0);
} else {
action.accept(random.nextInt(range) + 1);
}
return true;
}
},
false);
}
有相当多的样板,但您可以在tryAdvance 中看到算法的核心。如果没有剩余数字,则返回false,表示流结束。否则,它会发出一个数字,它有一定的概率(从 60% 开始)为零,否则是所需范围内的随机数。随着更多的零被发射,分子下降到零。如果已经发出了足够多的零,则分数变为零并且不再发出零。
如果发出的零很少,分母会下降,直到它更接近分子,从而增加发出零的概率。如果发出的零足够少,最终所需的零数量等于剩余数字的数量,因此分数的值变为 1.0。如果发生这种情况,则流的其余部分为零,因此将始终发出足够的零以满足要求。这种方法的好处是不需要收集数组中的所有数字并打乱它们或类似的东西。
这样调用方法:
IntStream is = randomWithPercentZero(1_000_000, 0.60, 100);
这会得到一个 1,000,000 个整数的流,其中 60% 是零,其余的在 1-100(含)范围内。