在某个范围内生成随机整数(不包括一个特定的数字)

18

我想在一个范围内获得一个随机数,但要排除一个数字(例如从1到1000排除577)。我已经搜索了解决方案,但是没有解决我的问题。

我想要像这样的东西:

Math.floor((Math.random() * 1000) + 1).exclude(577);

我尽量避免使用 for 循环来创建数组,因为数组的长度总是不同的(有时为1到10000,有时为685到888555444等),而生成它的过程可能会耗费太多时间。

我已经尝试了:

我该如何实现这个目标?


3
我不理解你所遇到的问题,以及为什么你发布的解决方案对你来说都无法运作。一个简单的while循环来检查禁止的数字是否存在,难道不能解决这个问题吗? - j08691
8
从1到99999中随机生成一个数字,如果这个数字大于等于577,则加1。 - ebyrob
2
@ebyrob 好的,但如果数字是99999,我加1会导致错误,因为10000不存在。我不想创建“if”条件。 - P. Frank
1
但从逻辑上讲,循环是最合理的。获取一个随机数,这个数等于禁止数字吗?如果是,那么获取一个新的随机数。除非你是从不包含禁止数字的数组之类的东西中随机选择,否则你必须在每次选择时进行检查。 - j08691
1
生成一个数字,如果它在列表中,则生成一个新的数字,继续这个过程直到你得到一个不在列表中的数字。我非常怀疑它会一直生成557... - epascarello
显示剩余4条评论
8个回答

32

在特定范围内([a, b]),除了一个值 c,获得随机整数的最快方法是在 ab-1 之间生成它,如果它大于或等于 c,则将其增加一。

下面是一个可行的函数:

function randomExcluded(min, max, excluded) {
    var n = Math.floor(Math.random() * (max-min) + min);
    if (n >= excluded) n++;
    return n;
}

这个解决方案的复杂度为O(1)。

是的,但 n++ 不会得到随机数,而是随机数加 1。 - P. Frank
3
这就是诀窍!例如,假设你想要从1到10,但不包括5。你生成1到9的数字,然后如果这个数字大于等于5,就加上1。这样做,你只能得到[1, 2, 3, 4, 6, 7, 8, 9, 10],这正是你想要的。所有的数字生成的概率相同,而5将永远不会被生成。 - Marco Bonelli
我选择了你的解决方案,但@guest271314的解决方案对我来说太好了。谢谢。 - P. Frank
1
如果我想传递一个数字数组(任意大小)..比如5,6和7要被排除,该怎么做?我该如何处理呢? - Bmbariah
为什么大于号很重要?如果n=excluded,为什么我们不能只增加随机数?@MarcoBonelli - Chirag
@Chirag 如果你这样做,就永远无法获得最大值。 - Marco Bonelli

5

一种可能性是不加1,如果出现该数字,则分配最后一个可能的值。

例如:

var result = Math.floor((Math.random() * 100000));
if(result==577) result = 100000;

这样一来,您就不需要重新启动随机方法,而是重复使用它。并且达到了作为随机数的目标。


这个似乎也可以工作。不确定赋值是否比加法更快,但是很好的变化。为什么要点踩?我认为对于多个排除项,这实际上会更快。(而且不需要排序) - ebyrob
这是唯一正确的算法,但下面Marco的版本更加优雅。 - Fattie

3
如@ebyrob所建议,您可以创建一个函数,该函数将较小集合中的值映射到较大集合中,并通过为大于等于每个值的值添加1来排除值:
// min - integer
// max - integer
// exclusions - array of integers
//            - must contain unique integers between min & max
function RandomNumber(min, max, exclusions) {
    // As @Fabian pointed out, sorting is necessary 
    // We use concat to avoid mutating the original array
    // See: https://dev59.com/w2kw5IYBdhLWcg3w2-XH
    var exclusionsSorted = exclusions.concat().sort(function(a, b) {
        return a - b
    });

    var logicalMax = max - exclusionsSorted.length;
    var randomNumber = Math.floor(Math.random() * (logicalMax - min + 1)) + min;

    for(var i = 0; i < exclusionsSorted.length; i++) {
        if (randomNumber >= exclusionsSorted[i]) {
            randomNumber++;
        }
    }

    return randomNumber;
}

示例 Fiddle

此外,我认为 @JesusCuesta 的回答提供了更简单的映射,并且更好。

更新:我的原始答案有许多问题。


没错,逻辑上的结论是在较小的集合上循环。 - ebyrob
@Fabian 谢谢你指出这个问题。我已经相应地更新了这个函数。 - Stryner
这对我来说太棒了。谢谢。 - P. Frank

2
你可以继续生成数字,直到找到符合你需求的为止:
function randomExcluded(start, end, excluded) {
    var n = excluded
    while (n == excluded)
        n = Math.floor((Math.random() * (end-start+1) + start));
    return n;
}

myRandom = randomExcluded(1, 10000, 577);

顺便说一句,这并不是最好的解决方案,请查看我的另一个答案以获取更好的解决方案!

1
这可能会导致无限的减速潜力。 - ebyrob
是的,如果你从数学角度考虑的话,但由于JS可以在微秒内生成成千上万个随机数,所以这永远不会变慢。 - Marco Bonelli
1
鉴于你知道下面的正确答案,你很惊讶地建议这个Marco被删除。 - Fattie
@JoeBlow 这仍然是一个答案,没有必要删除它,因为它像许多其他答案一样正确回答了问题。 - Marco Bonelli

1
进一步解释@Jesus Cuesta的答案:

function RandomNumber(min, max, exclusions) {
    var hash = new Object();
    for(var i = 0; i < exclusions.length; ++i ) {  // TODO: run only once as setup
       hash[exclusions[i]] = i + max - exclusions.length;
    }
    var randomNumber = Math.floor((Math.random() * (max - min - exclusions.length)) + min);
    if (hash.hasOwnProperty(randomNumber)) {
       randomNumber = hash[randomNumber];
    }
    return randomNumber;
}

注意:仅当 max - exclusions.length > 最大排除值时,此方法才有效。非常接近了。

0
生成一个随机数,如果与排除的数字匹配,则添加另一个随机数(-20到20)。
var max = 99999, min = 1, exclude = 577;
var num = Math.floor(Math.random() * (max - min)) + min ;
while(num == exclude || num > max || num < min ) {
    var rand = Math.random() > .5 ? -20 : 20 ;
    num += Math.floor((Math.random() * (rand));
}   

0
import random

def rng_generator():
  a = random.randint(0, 100)
  if a == 577:
    rng_generator()
  else:
    print(a)

#main()
rng_generator()

-2

从计算中排除数字:

function toggleRand() {
  // demonstration code only.
  // this algorithm does NOT produce random numbers.
  // return `0` - `576` , `578` - `n`  
  return [Math.floor((Math.random() * 576) + 1)
          ,Math.floor(Math.random() * (100000 - 578) + 1)
         ]
         // select "random" index 
         [Math.random() > .5 ? 0 : 1];
}

console.log(toggleRand());


或者使用 String.prototype.replace()RegExp /^(577)$/ 匹配应从结果中排除的数字;利用 new Date().getTime()isNaN()String.prototype.slice() 将其替换为范围内的另一个随机数 [0-99]

console.log(
  +String(Math.floor(Math.random()*(578 - 575) + 575))
  .replace(/^(577)$/,String(isNaN("$1")&&new Date().getTime()).slice(-2))
);


也可以使用String.prototype.match()来过滤结果:

console.log(
  +String(Math.floor(Math.random()*10)) 
  .replace(/^(5)$/,String(isNaN("$1")&&new Date().getTime()).match(/[^5]/g).slice(-1)[0])
);


@Fabian 可以使用 Math.random() > .5 ? 0 : 1 来选择在 01 之间返回随机索引。 - guest271314
是的,但例如100比900具有更高的概率。我认为这不是我们想要的。 - Fabian Schmitthenner
2
这个解决方案非常糟糕!将范围分成两部分,然后根据 Math.random() > .5 ? 0 : 1 在第一部分或第二部分中查找将 不会 生成纯随机数,因为获得每个数字的概率不相等。以 [Math.floor((Math.random() * 1) + 1), Math.floor(Math.random() * (100000 - 3 + 1))][Math.random() > .5 ? 0 : 1] 为例:它将在50%的情况下生成 1 - Marco Bonelli
@MarcoBonelli “不会生成纯随机数” Math.random() 会生成“纯随机数”吗? - guest271314
2
它根本不会生成随机数。当非数学家对此类事情进行完全胡言乱语的猜测时,真是一种遗憾:问题在于,这种荒谬的言论将在互联网上流传1000年。在这里发帖的人应该只需点击删除以求善良。请注意,这是计算机科学中一个非常著名的问题,其解决方案绝对微不足道。以下是一个详细的,冗长的解释和源代码http://stackoverflow.com/a/35315960/294884。同时,正确答案也已经在下面给出https://dev59.com/PVsX5IYBdhLWcg3wJMrb#34184614。 - Fattie
显示剩余22条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接