【问题标题】:How can I generate a random number within a range but exclude some?如何生成一个范围内的随机数但排除一些?
【发布时间】:2011-09-20 13:16:45
【问题描述】:

如何生成一个范围内的随机数但排除一些,而不继续生成并检查生成的数字是否是我想要排除的数字之一?

【问题讨论】:

  • 你想要一个 int 或 double 随机数?
  • 您的范围内是否有太多排除项,以至于很有可能达到排除项?

标签: java random


【解决方案1】:

每次不重新生成随机数的一种可能解决方案是使用以下算法:

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)

它会在startend(包括两者)之间生成一个随机数(int),并且不会为您提供数组exclude 中包含的任何数字。所有其他数字都以相同的概率出现。请注意,必须满足以下约束条件:exclude 按升序排序,并且所有数字都在提供的范围内,并且它们都相互不同。

【讨论】:

  • +1 同样的想法。但我更愿意在循环内检查random &lt; ex,然后立即返回random。否则执行random++ 并继续循环。应该在大型排除数组上表现更好。
  • @Fatal,@Howard,这是一个非常聪明的解决方案。你是从哪里知道的,或者你是怎么想到的?起初我确信这行不通,但在逐步完成之后,我发现它确实有效并且给我留下了深刻的印象。对于日常代码,我不希望在我们的代码库中看到这一点,因为它以牺牲可读性为代价提供了速度。
  • 这种方法让我着迷。我对此做了一些基准测试:do{ draw=start+rnd.nextInt(end);} while (Arrays.binarySearch(ex, draw) &gt;=0);,发现有时搜索数组更快。随着排除与池的比率接近 1(例如,在 11 个池中排除 10 个数字),搜索数组变得非常慢(1 亿次迭代需要 56 秒,而使用上述方法需要 3.5 秒)但是如果将池扩展到 1000,例如搜索数组更快(4.1s vs 4.7s)。再一次,这样做了 1 亿次。上述单次大约需要 10000 纳秒,而搜索需要 20000 纳纳。
  • 我不相信未排除的数字之间的分布是均匀的。如果我的范围是 1,2,3 而我排除了 2,那么 3 会不会出现 66% 的时间而 1 仅出现 33% 的时间?
  • 我想对您的回答发表评论。您的解决方案仅在您的 ex 数组已订购时才有效,否则将无法正常工作
【解决方案2】:
/**
 * @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;
}

这个想法是将生成随机数的范围缩小到开始和结束之间的差减去该范围内被排除的数字的计数。

因此,您得到的范围长度与可能的有效数字的计数相同。换句话说:你已经从范围内移除了所有的洞。

生成随机数后,您必须将“孔”放回范围内。只要排除的数字小于或等于生成的数字,就可以通过增加生成的数字来实现这一点。较低的排除数字是生成数字之前范围内的“洞”。对于该数字之前的每个洞,生成的数字都会向右移动。

【讨论】:

    【解决方案3】:

    您可以遵循随机化数字的最佳方法,其中一些方法是先选择您想要的数字,然后随机选择所选数字。例如,在伪代码中:

    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;
    }
    

    这都是伪代码,不要复制粘贴!

    【讨论】:

      【解决方案4】:

      我认为额外的问题是:您要排除哪些数字?它们代表某种范围还是完全随机

      如果您想忽略一系列数字,您可以从仅代表有效数字的几组中生成随机数:

      rand(1,9);
      rand(15,19);
      rand(22,26);
      

      这样你就确定你永远不会选择排除:27

      然后,当你得到你的 3 个号码时,你可以再次随机选择其中一个。

      如果排除的数字到处都是,恐怕你每次都必须对照某种排除数字的集合来检查它。

      【讨论】:

      • 我不得不撤回我之前的声明。虽然这似乎是一个好主意,但它实际上会扭曲结果,除非每个“选定”范围的大小相同。假设我想从 0 到 10 的范围内排除数字 2,数字 0 和 1 将比其余数字出现两次且频繁。要解决此问题,您必须倾向于选择来自更大范围的随机数,但我不确定如何适应这种情况并保持均匀分布。
      【解决方案5】:

      创建一个不带范围限制的随机函数输出的映射,并将其映射到您想要的带限制范围内。

      例如,如果我想要一个从 1 到 10 的随机整数,但从不想要 7,我可以这样做:

      int i = rand(1, 9);
      if i>=7
        i++;
      return i;
      

      只要确保映射为 1:1,就可以避免扭曲 rand 函数的随机性。

      【讨论】:

      • 反过来会更好:创建从 1 到 8 的数字并将 7 和 8 映射到 8 和 9。这样可以保持分布。
      • @keuleJ:你忘了映射 9 到 10 此外,这只是一个简单的例子。您想出的任何地图都将特定于您的算法。只要您的映射是一致的(每个可能的输入映射到相同数量的输出,并且每个所需的输出映射到相同数量的可能输入),您就可以保持分布。
      • @Ax 你是对的。但是您将 7 和 8 映射到 8。在这类算法中很容易弄乱分布...
      • @keuleJ:不,我没有。它与霍华德的代码做同样的事情,但规模很小。
      【解决方案6】:

      根据您排除的随机数列表的大小,我会生成您的数字并检查它是否在排除的数字数组中 - 如果是,则丢弃它。我知道您不想每次都检查,但除了明确指定范围之外,我想不出其他方法,如果您排除的数字超过 5 个,那可能会更糟。

      【讨论】:

        【解决方案7】:

        可以工作并适用于 int 和 double 数字的东西可能像:

        public int getRandomNumberWithExclusion( int start, int end )
        {
          Random r = new Random();
          int result = -1;
        
          do
          {
              result = start + r.nextInt( end - start );
          }//do
          while( !isAllowed( result ) );
        
          return result;
        
        }//met
        
        private boolean isAllowed( int number )
        {
           //your test for restricted values here
        }//met
        

        问候, 斯蒂芬

        【讨论】:

        • "[...] 不继续生成并检查生成的数字是否是我要排除的数字之一"
        【解决方案8】:

        排除数字应带有范围参数

        private int GiveMeANumber(int range,int... exclude)
        {
        
            Set<Integer> integers=new HashSet<>();
            int count=range;
        
            for(int i=0;i<count;i++)
                integers.add(i);
        
            integers.removeAll(Arrays.asList(exclude));
        
        
            int index = new Random().nextInt(range - exclude.length);
        
            count=0;
        
            for (int value:integers){
                if(count==index)
                    return value;
        
                count++;
            }
        
        
            return 0;
        }
        

        【讨论】:

          【解决方案9】:

          示例:val random = arrayOf((-5..-1).random(), (1..10).random()).random() 在此示例中,您排除了介于 -5 和 10 之间的数字中的 0

          【讨论】:

            【解决方案10】:

            这是最可靠的方法。

            使用您的范围创建一个数组,删除所有排除元素

            选择一个随机索引并从该位置返回值。

            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);
            }
            

            范围包括开始和结束

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2018-04-08
              • 2014-01-29
              • 1970-01-01
              • 2011-08-02
              • 1970-01-01
              • 2019-10-30
              • 1970-01-01
              相关资源
              最近更新 更多