【问题标题】:Random number, which is not equal to the previous number随机数,不等于前一个数
【发布时间】:2016-10-15 07:24:23
【问题描述】:

我需要获取随机数,但它不应该等于之前的数字。这是我的一段代码。但它不起作用。

function getNumber(){
  var min = 0;
  var max = 4;
  var i;
  i = Math.floor(Math.random() * (max - min)) + min;
  if (i=== i) {
    i = Math.floor(Math.random() * (max - min)) + min;
  }
  return i;
};

console.log(getNumber());

【问题讨论】:

  • i=== i 始终为真,因此您始终执行 if 内部的内容。
  • ` i=== i` 将始终返回 true ,因此将始终执行对 i 的第二次分配。将以前的数字存储在变量中,然后将其与新数字进行比较,而不是将新数字与自身进行比较
  • 获取所需长度的数组并打乱数组.. IMO,每次询问数字时最好避免while循环..
  • 这个问题是说不要重复之前的数字还是之前的所有数字?两者的逻辑会有所不同。

标签: javascript


【解决方案1】:

这个答案提出了三个尝试

  1. 具有函数getNumberlast的属性的简单版本,用于存储最后一个随机值。

  2. minmax 值上使用闭包的版本,如果max 小于min,则会引发异常。

  3. 一个结合了闭包和保留所有随机值的想法的版本,并在合适的时候使用它。


一个

您可以使用getNumber 的属性来存储最后一个数字并使用do ... while 循环。

function getNumber() {
    var min = 0,
        max = 4,
        random;

    do {
        random = Math.floor(Math.random() * (max - min)) + min;
    } while (random === getNumber.last);
    getNumber.last = random;
    return random;
};

var i;
for (i = 0; i < 100; i++) {
    console.log(getNumber());
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

两个

另一个在区间和最后一个随机值上有闭包的提议。

function setRandomInterval(min, max) {
    var last;
    if (min >= max) {
        throw 'Selected interval [' + min + ', ' + max + ') does not work for random numbers.';
    }
    return function () {
        var random;
        do {
            random = Math.floor(Math.random() * (max - min)) + min;
        } while (random === last);
        last = random;
        return random;
    };
}

var i,
    getRandom = setRandomInterval(0, 4);

for (i = 0; i < 100; i++) {
    console.log(getRandom());
}

setRandomInterval(4, 4); // throw error
.as-console-wrapper { max-height: 100% !important; top: 0; }

三个

这个提议使用的想法是最小化新随机数的调用。它适用于两个变量,value 用于持续相同的随机值,count 用于保存相同值的计数。

如果给出了保存的计数并且该值不等于最后一个值,则该函数首先查找。如果发生这种情况,则返回保存的值并减少计数。

否则会生成一个新的随机数并按上述方式进行检查(第一个提议)。如果数字等于最后一个值,则计数增加并继续生成新的随机值。

因此,几乎所有以前生成的随机值都被使用了。

function setRandomInterval(min, max) {
    var last,      // keeping the last random value
        value,     // value which is repeated selected
        count = 0, // count of repeated value
        getR = function () { return Math.floor(Math.random() * (max - min)) + min; };

    if (min >= max) {
        throw 'Selected interval [' + min + ', ' + max + ') does not work for random numbers.';
    }
    return function () {
        var random;
        if (count && value !== last) {
            --count;
            return last = value;
        }
        random = getR();
        while (random === last) {
            value = random;
            ++count;
            random = getR();
        }
        return last = random;
    };
}

var i,
    getRandom = setRandomInterval(0, 4);

for (i = 0; i < 100; i++) {
    console.log(getRandom());
}
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 您可以在if 条件下使用逻辑来代替igetNumber.last 的递增或递减do..while 循环,以消除对getNumber 的额外调用的需要;另见Random integer in a certain range excluding one number
  • 我不明白你。你的意思是哪个 if 条件(没有)?
  • 当前实现调用getNumber超过100次plnkr.co/edit/3GHnnhbkf5F8f0fRx9mX?p=preview。您可以删除循环,在if 语句中使用逻辑条件来递增或递减igetNumber.last 以调用getNumber 等于for 循环调用以返回预期结果plnkr.co/edit/3GHnnhbkf5F8f0fRx9mX?p=preview
  • 哦,我想,我明白了。最后一个 for 循环仅用于演示目的,getNumber 仅被调用 100 次,而不是更多,因为您也计算了 do 循环。
  • 我只调用了getNumbergetNumber.last 的属性,而不是再次调用函数。
【解决方案2】:

以下方法在 [min, max] 范围内生成一个新的随机数,并确保该数字与前一个不同,没有循环和递归调用(Math.random() 只调用一次):

  • 如果存在先前的数字,则将最大值减一
  • 在范围内生成一个新的随机数
  • 如果新数字等于或大于前一个数字,则添加一个
    (另一种选择:如果新数字等于前一个数字,则将其设置为 max + 1)

为了将之前的数字保留在闭包中,getNumber 可以在 IIFE 中创建:

// getNumber generates a different random number in the inclusive range [0, 4]
var getNumber = (function() {
  var previous = NaN;
  return function() {
    var min = 0;
    var max = 4 + (!isNaN(previous) ? -1 : 0);
    var value = Math.floor(Math.random() * (max - min + 1)) + min;
    if (value >= previous) {
      value += 1;
    }
    previous = value;
    return value;
  };
})();

// Test: generate 100 numbers
for (var i = 0; i < 100; i++) {
  console.log(getNumber());
}
.as-console-wrapper {
  max-height: 100% !important;
  top: 0;
}

[min,max] 范围是通过在以下语句中将 1 加到 max - min 中得到的 inclusive

var value = Math.floor(Math.random() * (max - min + 1)) + min;

这不是问题中的要求,但我觉得使用包含范围更自然。

【讨论】:

  • 我不认为这段代码,即提供一个接受minmax 作为参数的函数真的很安全。如果您的函数接受参数,则意味着您期望有多个消费者。在这种情况下,不同的消费者会相互混淆状态(previous),从而可以为同一个消费者生成两个连续相等的值。我认为最简单的安全方法是创建一个工厂函数createGetNumber,它将接受minmax,并返回一个没有参数且唯一绑定到状态的函数。
  • @SergGr - 感谢您的评论。我可以将minmax 放回函数内部,因为不需要提供更改这些限制的能力。
  • @ConnorsFan,您可能想要明确声明您的方法保证从不包括前一个数字的数字集中随机抽取一个数字,即使在抽取的数字是前一个数字的情况下也是如此。就目前而言,它可能被误解为牺牲随机性的简单调整。
  • @TomasLangkaas - 感谢您的建议。我改写了我的答案,希望现在更清楚。
【解决方案3】:

首先函数应该与之前的值进行比较,现在我们只有i变量与自身进行比较。为了确保我们没有以前的值,我们需要在内部进行循环(在我的解决方案中是递归的),因为单个 if statement 不能让我们确定第二个随机数会不一样(存在机会)。您的数字集非常小,因此发生冲突的可能性很高,并且循环可能需要很少的执行。

function getNumber(prev){
  var min = 0;
  var max = 4;
  var next;
  
  next = Math.floor(Math.random() * (max - min)) + min;
  
  if (next===prev) {
    console.log("--run recursion. Our next is ="+next); //log only for test case
    next = getNumber(prev); //recursive
  }
  
  return next;
};

//test 100 times
var num=0;
for ( var i=0; i<100; i++){
  num=getNumber(num);
  console.log(num);
}

正如您在测试中看到的那样,我们永远不会有两个相同的值彼此相邻。我还添加了一些console.log 来显示递归需要运行多少次才能找到下一个与前一个不同的数字。

【讨论】:

    【解决方案4】:

    通用解决方案

    跟踪最后生成的号码。生成新号码时,请检查它是否与上一个号码不同。如果不是,继续生成新的数字,直到不同,然后输出。

    工作演示

    var getNumber = (function(){
      var min = 0;
      var max = 4;
      var last = -1;
      return function(){
        var current;
        do{
          // draw a random number from the range [min, max]
          current = Math.floor(Math.random() * (max + 1 - min)) + min;
        } while(current === last)
        return (last = current);
      }
    })();
    
    // generate a sequence of 100 numbers,
    // see that they all differ from the last
    
    for(var test = [], i = 0; i < 100; i++){
      test[i] = getNumber();
    }
    console.log(test);

    关于计算效率的评论

    正如在 cmets 和其他答案中所讨论的,上述方法的一个潜在缺点是,如果生成的数字等于前一个,它可能需要多次尝试生成随机数。请注意,需要多次尝试的可能性非常低(它遵循快速下降的geometric distribution)。出于实际目的,这不太可能产生任何明显的影响。

    但是,可以避免多次尝试生成新的随机数,方法是直接从 [min, max] 范围内的数字集中抽取一个随机数,不包括先前抽取的数字:这在由@ConnorsFan 回答,每次函数调用只生成一个随机数,同时仍然保留随机性。

    【讨论】:

    • 拨打getNumber多少次才能生成100个号码?
    • getNumber 为每个数字调用一次,但 do/while 循环理论上可以运行一段时间。实际上,它通常只运行一次。有 5 个可能的数字,大约需要 5 次重复才能获得不同的数字。
    • 这不是 OP 关心的问题,尽管在几个答案中发现了副作用,包括这个用户的第一个答案 stackoverflow.com/a/40057625。理想情况下,只需调用 100 次函数或循环即可满足要求,例如,请参阅 stackoverflow.com/a/40102421
    • @guest271314,我没有得到这个要求。在这种情况下,ConnorsFan 似乎做对了。
    • 你是对的。未指定为赏金要求的一部分。前段时间在stackoverflow.com/questions/40056297/… 的评论中注明。全体投票将决定目前的赏金stackoverflow.com/questions/40056297/…
    【解决方案5】:

    您需要一个比getNumber 函数的本地变量范围更大的变量。试试:

    var j;
    function getNumber(){
      var min = 0;
      var max = 4;
      var i = Math.floor(Math.random() * (max - min)) + min;
      if (j === i) {
        i = getNumber();
      }
      j = i;
      return i;
    };
    

    【讨论】:

      【解决方案6】:

      从一开始就从可能值集中删除前一个值。

      function getNumber(previous) {
        var numbers = [0, 1, 2, 3, 4];
        if (previous !== undefined) {
            numbers.splice(numbers.indexOf(previous), 1);
        }
      
        var min = 0;
        var max = numbers.length;
        var i;
        i = Math.floor(Math.random() * (max - min)) + min;
      
        return numbers[i];
      };
      
      //demonstration. No 2 in  a row the same
      var random;
      for (var i = 0; i < 100; i++) {
        random = getNumber(random);
        console.log(random);
      }
      

      【讨论】:

      • 好主意,不需要做任何循环,从数字集中删除是有意义的。
      【解决方案7】:

      您可以使用@NinaScholz 模式的实现,其中先前的值存储为调用函数的属性,用条件逻辑代替循环的当前返回值递增或递减。

      如果当前值等于先前返回的值,则在当前函数调用期间更改当前值,而不使用循环或递归,然后返回更改后的值。

      var t = 0;
      
      function getNumber() {
        var min = 0,
          max = 4,
          i = Math.floor(Math.random() * (max - min)) + min;
      
        console.log(`getNumber calls: ${++t}, i: ${i}, this.j: ${this.j}`);
      
        if (isNaN(this.j) || this.j != i) {
          this.j = i;
          return this.j
        } else {
      
          if (this.j === i) {
      
            if (i - 1 < min || i + 1 < max) {
              this.j = i + 1;
              return this.j
            }
      
            if (i + 1 >= max || i - 1 === min) {
              this.j = i - 1;
              return this.j
            }
      
            this.j = Math.random() < Math.random() ? --i : ++i;
            return this.j
      
          }
        }
      };
      
      for (var len = 0; len < 100; len++) {
        console.log("random number: ", getNumber());
      }

      【讨论】:

        【解决方案8】:

        此解决方案使用 ES6 生成器并避免生成随机数,直到找到符合前提条件的随机数(两个相关数必须不同)。

        主要思想是有一个带有数字的数组和一个带有索引的数组。然后你会得到一个随机索引(为了符合前提条件,索引的数组将是用先前选择的索引过滤索引数组的结果)。返回值将是与数字数组中的索引对应的数字。

        function* genNumber(max = 4) {// Assuming non-repeating values from 0 to max
          let values = [...Array(max).keys()],
              indexes = [...Array(max).keys()],
              lastIndex,
              validIndexes;
        
          do {
            validIndexes = indexes.filter((x) => x !== lastIndex);
            lastIndex = validIndexes[Math.floor(Math.random() * validIndexes.length)];
            yield values[lastIndex];
          } while(true);
        }
        
        var gen = genNumber();
        for(var i = 0; i < 100; i++) {
          console.log(gen.next().value);
        }
        

        这里是fiddle,如果您想查看结果。

        【讨论】:

          【解决方案9】:

          将之前生成的随机数保存在数组中

          // global variables
           tot_num = 10; // How many number want to generate?
           minimum = 0; // Lower limit
           maximum = 4; // upper limit
           gen_rand_numbers = []; // Store generated random number to prevent duplicate.
          
          /*********** **This Function check duplicate number** ****************/
           function in_array(array, el) {
          	for (var i = 0; i < array.length; i++) {
          		if (array[i] == el) {
          			return true;
          		}
          	}
          	return false;
          }
          
          /*--- This Function generate Random Number ---*/
          function getNumber(minimum, maximum) {
          	var rand = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
          	if (gen_rand_numbers.length <= (maximum - minimum)) {
          		if (!in_array(gen_rand_numbers, rand)) {
          			gen_rand_numbers.push(rand);
          			//alert(rand)
                console.log(rand);
          			return rand;
          		} else {
          			return getNumber(minimum, maximum);
          		}
          	} else {
          		alert('Final Random Number: ' + gen_rand_numbers);
          	}
          
          }
          
          /*--- This Function call random number generator to get more than one random number ---*/
          
          function how_many(tot_num) {
          	for (var j = 0; j < tot_num; j++) {
          		getNumber(minimum, maximum);
          	}
          	
          }
          <script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js" > </script>
          
          	<input type = "button"	onclick = "how_many(4)" value = "Random Number" >

          【讨论】:

            【解决方案10】:

            您可以使用linear congruential generator 的增强实现。

            线性同余生成器 (LCG) 是一种算法,它产生一系列伪随机数,通过不连续的分段线性方程计算得出。

            以下函数返回一个种子随机数以及最小值和最大值:

            Math.seededRandom = function(seed, min, max) {
              max = max || 1;
              min = min || 0;
            
              // remove this for normal seeded randomization
              seed *= Math.random() * max;
            
              seed = (seed * 9301 + 49297) % 233280;
              let rnd = seed / 233280.0;
            
              return min + rnd * (max - min);
            };
            

            在您的情况下,因为您不希望新数字与以前的数字相同,所以您可以将之前生成的数字作为种子传递。

            下面是一个生成 100 个随机数的示例:

            Math.seededRandom = function(seed, min, max) {
              max = max || 1;
              min = min || 0;
              
              // remove this for normal seeded randomization
              seed *= Math.random() * max;
              
              seed = (seed * 9301 + 49297) % 233280;
              let rnd = seed / 233280.0;
            
              return min + rnd * (max - min);
            };
            
            
            
            let count = 0;
            let randomNumbers = [];
            let max = 10;
            do {
              let seed = (randomNumbers[randomNumbers.length -1] || Math.random() * max);
              
              randomNumbers.push(Math.seededRandom(seed, 0, max));
              count++;
            } while (count < 100)
            
            console.log(randomNumbers);

            【讨论】:

            • 原始问题考虑0,1,2,3,或整数。当前答案在连续编号时返回重复,但小数部分是唯一的3:4.5591053733292854,:4 :4.8499226756444117:3.9133924461395226,8:3.023985816875585
            【解决方案11】:

            一个有趣的答案,在一行中生成从 0 到 4 的数字:

            console.log(Math.random().toString(5).substring(2).replace(/(.)\1+/g, '$1').split('').map(Number));

            解释:

            Math.random() //generate a random number
                .toString(5) //change the number to string, use only 5 characters for it (0, 1, 2, 3, 4)
                .substring(2) //get rid of '0.'
                .replace(/(.)\1+/g, '$1') //remove duplicates
                .split('') //change string to array
                .map(Number) //cast chars into numbers
            

            还有一个带有生成器的更长版本:

            let Gen = function* () {
              const generateString = (str) => str.concat(Math.random().toString(5).substring(2)).replace(/(.)\1+/g, '$1');
              let str = generateString('');
              let set = str.split('').map(Number);
              
              while (true) {
                if (set.length === 0) {
                  str = generateString(str).substring(str.length);
                  set = str.split('').map(Number);
                }
                yield set.pop();
              }
            }
            
            let gen = Gen();
            
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);
            console.log(gen.next().value);

            【讨论】:

              【解决方案12】:
              function getNumber(){
                  var min = 0;
                  var max = 4;
                  var i;
                  i = Math.floor(Math.random() * (max - min)) + min;
                  while(i==getNumber.last)
                      i = Math.floor(Math.random() * (max - min)) + min;
                  getNumber.last=i;
                  return i;
              };
              
              console.log(getNumber());
              

              【讨论】:

                【解决方案13】:

                试试这个

                var prev_no = -10;
                
                function getNumber(){
                    var min = 0;
                    var max = 4;
                    var i;
                    i = Math.floor(Math.random() * (max - min)) + min;
                    while (i == prev_no) {
                        i = Math.floor(Math.random() * (max - min)) + min;
                        prev_no = i;
                    }
                
                    return i;
                };
                
                console.log(getNumber());
                

                【讨论】:

                • prev_no 的初始化值是随机变量最小-最大范围的一部分。所以在第一个生成的随机数为0的情况下,你会得到一个不正确的结果
                • 它仍然是一个数字,可能需要在每次修改最小/最大值时进行更改。另外,prev_no=i 行不应该在 while 循环之外吗?
                • 你的函数在作用域变量之外发生了变异,它不是纯函数。
                • @MaciejSikora,根据定义没有输入参数的“纯函数”不能产生不同的输出。虽然我同意将prev_no 放在某个本地范围内会更好。但是如果这段代码是某个模块的一部分,并且已经被封装到一个作用域中,我认为这是可以接受的。
                【解决方案14】:

                您可以使用PromiseArray.prototype.forEach()setTimeout。创建并迭代将.length 设置为max 的数组;在.forEach() 回调中使用setTimeout 并将duration 设置为随机值,以将数组的索引推送到新数组,以实现新数组中索引的非均匀分布。从getNumber 函数返回已解析的Promise,其中Promise.then() 值将是.lengthmax 的数组,具有来自.forEach() 回调的随机index 作为没有重复条目的值。

                function getNumber(max) {
                  this.max = max;
                  this.keys = [];
                  this.arr = Array.from(Array(this.max));
                  this.resolver = function getRandom(resolve) {
                    this.arr.forEach(function each(_, index) {
                      setTimeout(function timeout(g) {
                        g.keys.push(index);
                        if (g.keys.length === g.max) {
                          resolve(g.keys)
                        };
                      }, Math.random() * Math.PI * 100, this);
                    }, this)
                  };
                  this.promise = new Promise(this.resolver.bind(this));
                }
                
                var pre = document.querySelector("pre");
                
                var random1 = new getNumber(4);
                random1.promise.then(function(keys) {
                  pre.textContent += keys.length + ":\n";
                  keys.forEach(function(key) {
                    pre.textContent += key + " ";
                  })
                });
                
                var random2 = new getNumber(1000);
                random2.promise.then(function(keys) {
                  pre.textContent += "\n\n";
                  pre.textContent += keys.length + ":\n";
                  keys.forEach(function(key) {
                    pre.textContent += key + " ";
                  })
                });
                pre {
                  white-space: pre-wrap;
                  width: 75vw;
                }
                &lt;pre&gt;&lt;/pre&gt;

                【讨论】:

                  【解决方案15】:

                  除非您执行数据库查询以检查新号码是否存在,否则您无法做到这一点。如果存在,请重复该过程。

                  生成唯一随机数的一种架构可能性是生成两个随机数并组合字符串。

                  例如:

                  rand_num1 = rand(5);
                  rand_num2 = rand(4);
                  

                  然后结合 rand_num1 和 rand_num2 这更像是唯一的

                  实际例子:

                  (23456)(2345)
                  (23458)(1290)
                  (12345)(2345)
                  

                  同时增加位数以减少重复。

                  【讨论】:

                  • 进行数据库查询不是让事情变得过于复杂。我认为,使用静态变量会导致类似的输出。
                  • 已经发出了1000个随机数,但是现在如何在不存储之前创建的数字的情况下检查唯一性?
                  猜你喜欢
                  • 2014-12-27
                  • 1970-01-01
                  • 2020-10-15
                  • 2013-09-12
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-09-22
                  • 2022-11-21
                  • 1970-01-01
                  相关资源
                  最近更新 更多