【问题标题】:How to generate random values where a percentage of them are 0?如何生成其中百分比为 0 的随机值?
【发布时间】:2017-11-03 18:52:00
【问题描述】:

我创建一个随机流

Random random = new Random();
Stream<Integer> boxed = random.ints(0, 100000000).boxed();

但我需要生成的数字中有 60% 为 0,而其余的可以是真正随机的。我该怎么做?

编辑:

我只需要0-100之间的正数

1
2
0
0
9
0
0
1
12

【问题讨论】:

  • 您想随机生成 60% 的零吗?这是没有意义的
  • 60% 值 == 0 40% 值 == 随机
  • 正好 60%?或平均?即 60-40 拆分也是随机分布(掷硬币)还是更具确定性?
  • 您的问题缺乏精确性,因为目前的措辞是返回 60 个零,后跟 40 个随机数,如果可以提前知道大小,则可以满足要求。或者无限流的固定旋转。
  • 您需要编辑问题以澄清。你的意思是你需要 60% 的机会为每个元素生成一个零,还是你的意思是你需要一个已知大小的列表,其中 60% 是 (nonrandomly) 0?在前一种情况下,您不会得到正好 60% 的包含 0 的列表;列表中的每个元素都有 60% 的概率为零。在后一种情况下,您实际上只需要随机生成 40% 的长度,然后连接 60% 的长度 0(如有必要,可能会打乱结果)。后者是一个非常奇怪的要求。

标签: java random java-8


【解决方案1】:

我假设 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(含)范围内。

【讨论】:

  • 星期天早上干得不错 :)
【解决方案2】:

由于目标区间的大小可以被 10 整除,因此您可以指望生成数字的最后一位是均匀分布的。因此,这种简单的方法应该可行:

  • 生成 0..1000 范围内的数字
  • 如果随机数r的最后一位为0..5(含),则返回零
  • 否则返回r / 10

这是代码中的这种方法:

Stream<Integer> boxed = random.ints(0, 1000).map(r -> r%10 < 6 ? 0 : r/10).boxed();

Demo.

【讨论】:

  • 我喜欢这种方法。这可能是最有效的。但是,我担心它会推动过于“聪明”的界限,以至于无法立即看出你在做什么。
  • @Michael “太聪明”在处理随机分布时几乎是不可避免的。在这行代码的上方有一大堆 cmets。这里还有一个问题需要考虑 - 零的百分比将略高于 60%。具体来说,它将是 60.4%,其余 0.4% 来自序列中“自然”出现的零。
  • @ip696 你什么意思?所以有这么多元素?使用limit 方法。不过,任何事物的万亿次迭代都需要一段时间才能完成。
  • @ip696 这听起来本身就是一个有趣的问题。诸如“如何将无限 Java 流转换为稀疏 2D 矩阵”之类的内容可能会得到一些很好的答案。
  • @jpmc26 我认为它略微偏向于 0,因为 0 和 1000 都在分布中,因此与任何其他数字相比,可以多一个以 0 结尾的数字。将范围更改为 0,999 应该可以解决这个问题。
【解决方案3】:

您可以使用IntStream.map 并重新使用您的Random 实例来生成一个从0 到9(含)的随机数,如果它在前60% 中则返回零,否则返回生成的数字:

Stream<Integer> boxed = random.ints(0, 100)
                              .map(i -> (random.nextInt(10) < 6) ? 0 : i)
                              .boxed();

【讨论】:

  • 从流中选取的每个元素为 0 的概率为 60%,但这并不能保证流中的元素正好 60% 为 0
  • @AlexisC。我不明白你认为区别是什么
  • @AlexisC。流是无限的,所以没有办法精确到 60%。
  • 好吧,OP 要求 60% 的元素为 0,也就是说,如果我采用大小为 10 的流,他期望 6 个零和 4 个随机值。您的流可以生成 10 个随机值或 10 个零或 5 个零和 5 个随机值,但不能保证正好有 6 个零和 4 个随机值。
  • 是的,当然,(如果您要求 12 个数字,那么您可以提供 7 个零 = 58% 或 8 个零 = 66%)但您的流不能保证每次都会提供大约 60% 的零您使用它,这是 OP 提出的要求。
【解决方案4】:

构造 10 个对象。其中 6 个始终返回 0。其余 4 个根据您的规格随机返回。

现在随机选择一个对象并调用

List<Callable<Integer>> callables = new ArrayList<>(10);
for (int i = 0; i < 6; i++) {
    callables.add(() -> 0);
}
Random rand = new Random();
for (int i = 6; i < 10; i++) {          
    callables.add(() -> rand.nextInt());
}

callables.get(rand.nextInt(10)).call();

这是一种更简单的实现方式。您可以进一步优化它。

【讨论】:

  • 在这里使用List&lt;Callable&lt;Integer&gt;&gt; 而不是简单地使用List&lt;Integer&gt;&gt; 有什么优势。我相信list.get(rand.nextInt(10)); 会很好用吗?
  • List&lt;List&lt;Integer&gt;&gt;我没有关注,你想如何构建它?
  • 您可以在其中保存0 静态内容。您将如何处理 40% 的随机数据?
  • 我可能是错的,但是为 40% 的情况生成一个新的随机数有什么需要。 OP没有提到随机数不能是静态的?即使您使用 List&lt;Integer&gt; 并使用 rand.nextInt 添加 6 个元素,这 40% 的情况仍然是随机的
  • 我认为这不是 OP 所寻求的。除非 OP 指的是imgs.xkcd.com/comics/random_number.png
【解决方案5】:

为什么不生成这 60% 的数组(从零开始),而只是随机生成另外 40%:

List<Integer> toShuffle = IntStream
            .concat(Arrays.stream(new int[60_000_000]),
                    random.ints(40_000_000, 0, Integer.MAX_VALUE))
            .boxed()
            .collect(Collectors.toCollection(() -> new ArrayList<>(100_000_000)));

    Collections.shuffle(toShuffle);

【讨论】:

  • 必须对 1 亿个事物进行排序对于这种微不足道的事情来说是荒谬的开销。
  • @Michael 排序?可能是shuffle?
  • 对不起,我的错。所以比排序好几个数量级,但仍然很糟糕。
  • @Michael 没有实际测量 - 这个陈述是(不要误会我的意思),但它无关紧要
  • @Michael 说,似乎是我将提出的 JMH 测试的完美候选人
【解决方案6】:

如果您想要 60% 的零和 40% 的严格正数,您可以简单地使用模数检查:

Stream<Integer> boxed = IntStream.range(0, 100_000_000)
                            .map(i -> (i % 10 < 6) ? 0 : r.nextInt(Integer.MAX_VALUE) + 1)
                            .boxed();

之后您可能希望“洗牌”流,以避免每十个位置连续出现 6 个零。

【讨论】:

  • 不得不“洗牌”为我杀死了这个答案
  • 0 -1273777838 823897558 我只需要正面
  • @assylias 这可能是这里最干净的答案 + 1
【解决方案7】:

您可以通过对生成的值进行计数并产生零以达到所需的 60% 来控制您的流以准确生成 60% 的零。

public class RandomGeneratorSample {
    public static void main(String... strings) {
        Random random = new Random();
        Controll60PercentOfZero controll60 = new Controll60PercentOfZero();

        Stream<Integer> boxed = random.ints(0, 100).map(x -> controll60.nextValueMustBeZero(x) ? 0 : x).boxed();

        boxed.forEach(System.out::println);
    }

    static class Controll60PercentOfZero {
        private long count_zero = 1;
        private long count_not_zero = 1;

        public boolean nextValueMustBeZero(int x) {

            if (x == 0) {
                count_zero++;
            } else {
                count_not_zero++;
            }           
            boolean nextValueMustBeZero= (count_zero * 100 / count_not_zero) < 60;
            if(nextValueMustBeZero){
                count_zero++;
            }
            return nextValueMustBeZero;
        }
    }

}

【讨论】:

    【解决方案8】:

    有趣的问题。
    大多数其他答案都非常相关,每个答案都从不同的角度处理它。
    我愿意贡献。

    要获得一个恰好是0 60% 的Collection(而不是一个流)并且这些出现“伪随机”,您可以:

    • 声明并实例化一个List

    • 然后循环你想要添加的元素数量

    • 在其中,每 10 次迭代 6 次,您在列表中的随机索引处添加 0。否则,您将在 List 中的随机索引处添加一个随机值。

    缺点是大约 40% 的时间,nextInt() 被调用两次:一次生成值,另一次生成将值插入列表的索引。

    这是一个示例代码,它生成从 010001000 元素,其中 60% 是 0

    Random random = new Random();
    List<Integer> values = new ArrayList<>();
    
    for (int i = 0; i < 1000; i++) {
        int nextValue = i % 10 < 6 ? 0 : random.nextInt(1000) + 1;
        int indexInList = values.size() <= 1 ? 0 : random.nextInt(values.size() - 1);
        values.add(indexInList, nextValue);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-10
      • 1970-01-01
      • 1970-01-01
      • 2012-01-26
      • 1970-01-01
      相关资源
      最近更新 更多