Генерирование неповторяющихся случайных чисел в JS

У меня есть следующая функция

function randomNum(max, used){
 newNum = Math.floor(Math.random() * max + 1);

  if($.inArray(newNum, used) === -1){
   console.log(newNum + " is not in array");
   return newNum;

  }else{
   return randomNum(max,used);
  }
}

В основном я создаю случайное число от 1 до 10 и проверяю, было ли это число уже создано, добавив его в массив и проверив на нем новый созданный номер. Я называю это, добавляя его к переменной..

UPDATED:
for(var i=0;i < 10;i++){

   randNum = randomNum(10, usedNums);
   usedNums.push(randNum);

   //do something with ranNum
}

Это работает, но в Chrome я получаю следующую ошибку:

Uncaught RangeError: Maximum call stack size exceeded

Я думаю, это потому, что я вызываю функцию внутри себя слишком много раз. Это означает, что мой код не подходит.

Может ли кто-нибудь помочь мне с логикой? что лучший способ убедиться, что мои номера не повторяются?

Ответ 1

Если я правильно понимаю, вы просто ищете перестановку (т.е. числа, рандомизированные без повторов) чисел 1-10? Может быть, попробуйте создать рандомизированный список этих чисел, один раз, в начале, а затем просто проработайте свой путь через них?

Это вычислит случайную перестановку чисел в nums:

var nums = [1,2,3,4,5,6,7,8,9,10],
    ranNums = [],
    i = nums.length,
    j = 0;

while (i--) {
    j = Math.floor(Math.random() * (i+1));
    ranNums.push(nums[j]);
    nums.splice(j,1);
}

Итак, например, если вы искали случайные числа от 1 до 20, которые были даже четными, вы могли бы использовать:

nums = [2,4,6,8,10,12,14,16,18,20];

Затем просто прочитайте ranNums, чтобы вызвать случайные числа.

Это не рискует, что он все больше и больше будет искать неиспользуемые числа, как вы находили в своем подходе.

EDIT: после чтения этого и выполнения теста jsperf, кажется, что гораздо лучший способ сделать это - переключение Fisher-Yates Shuffle:

function shuffle(array) {
    var i = array.length,
        j = 0,
        temp;

    while (i--) {

        j = Math.floor(Math.random() * (i+1));

        // swap randomly chosen element with current element
        temp = array[i];
        array[i] = array[j];
        array[j] = temp;

    }

    return array;
}

var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]);

В принципе, это более эффективно, избегая использования "дорогостоящих" операций с массивами.

BONUS EDIT. Еще одна возможность заключается в использовании generators (предполагается, что поддержка):

function* shuffle(array) {

    var i = array.length;

    while (i--) {
        yield array.splice(Math.floor(Math.random() * (i+1)), 1)[0];
    }

}

Затем для использования:

var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]);

ranNums.next().value;    // first random number from array
ranNums.next().value;    // second random number from array
ranNums.next().value;    // etc.

где ranNums.next().value в конечном итоге будет оцениваться до undefined после того, как вы пройдете через все элементы в перетасованном массиве.

В целом это будет не так эффективно, как Fisher-Yates Shuffle, потому что вы все еще splice -в массиве. Но разница в том, что вы сейчас выполняете эту работу только тогда, когда вам это нужно, а не делаете все это заранее, поэтому в зависимости от вашего варианта использования это может быть лучше.

Ответ 2

Проблема в том, что по мере приближения к насыщению вы начинаете занимать больше времени и дольше, чтобы генерировать уникальный номер "случайно". Например, в приведенном выше примере max равен 10. После того, как использованный массив чисел содержит 8 чисел, потенциально может потребоваться много времени для поиска 9-го и 10-го. Вероятно, это место, где генерируется максимальная ошибка стека вызовов.

jsFiddle Demo showing iteration count being maxed

Итерируя внутри своей рекурсии, вы можете видеть, что большое количество выполнения происходит, когда массив полностью насыщен, но функция вызывается. В этом случае функция должна выйти.

jsFiddle Demo with early break

if( used.length >= max ) return undefined;

И последний способ выполнить итерационные проверки и бесконечную рекурсию будет выглядеть следующим образом: jsFiddle Demo

function randomNum(max, used, calls){
 if( calls == void 0 ) calls = 0;
 if( calls++ > 10000 ) return undefined;
 if( used.length >= max ) return undefined;
 var newNum = Math.floor(Math.random() * max + 1);
 if($.inArray(newNum, used) === -1){
   return newNum;
 }else{
   return randomNum(max,used,calls);
 }
}

Ответ 3

Это сделает то, что вы ищете:

let anArrayOfUniqueNumbers = [];

let numberGenerator = function(arr) {
  if (arr.length >= 10) return;
  let newNumber = Math.floor(Math.random() * 10 + 1);
  if (arr.indexOf(newNumber) < 0) {
    arr.push(newNumber);
  }
  numberGenerator(arr);
};

numberGenerator(anArrayOfUniqueNumbers);

У нас есть:

  • новый массив
  • функция, которая принимает массив в качестве аргумента
Эта функция будет:
  • Проверьте, имеет ли массив, на котором он работает, уже десять индексов, а если нет:
  • Генерация случайного числа от 1 до 10
  • Если этого случайного числа еще нет в массиве, вставьте его в массив
  • Запустите снова

Из-за защитного предложения (if (arr.length >= 10) return;) функция прекратит выполнение, как только параметры будут выполнены.

Ответ 4

function randomNumbers(max) {
    function range(upTo) {
        var result = [];
        for(var i = 0; i < upTo; i++) result.push(i);
        return result;
    }
    function shuffle(o){
        for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
        return o;
    }
    var myArr = shuffle(range(max));
    return function() {
        return myArr.shift();
    };
}

Построенный небольшой тест, попробуйте это на jsfiddle:

var randoms = randomNumbers(10),
    rand = randoms(),
    result = [];
while(rand != null) {
    result.push(rand);
    rand = randoms();
}
console.log(result);

Функция в случайном порядке любезно предоставлена ​​dzone.com.

Ответ 5

Вот как я это достигаю, используя underscore.js

Получить n целые числа от min до max. Где n - аргумент size.

var randomNonRepeatingIntFromInterval = function(min, max, size) {
    var values = [];

    while (values.length < size) {
      values.push(Math.floor(Math.random() * ( max - min + 1) + min));

      values = _.uniq(values);
    }

    return values;
  }

Ответ 6

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript Math.random()</h2>

<p>Math.random() returns a random number between 0 (included) and 1 (excluded):</p>

<p id="demo"></p>

<script>


var storeArray = []

function callRamdom(){
    var randomNumber = Math.floor(Math.random() * 5);   
    return randomNumber;
}

function randomStore(){ 

    var localValue = callRamdom()
    var status = false;
    for(i=0;i<5; i++){
    var aa = storeArray[i];
        if(aa!=localValue){
            console.log(storeArray[i]+"hhhhh"+ localValue); 
            if(i==4){
                status=true;        
            }

        }   
        else
        break;

    }

    if(status==true){

        storeArray.push(localValue);    
    }
    if(storeArray.length!=5){
        randomStore();
    }   

    return storeArray;
}



document.getElementById("demo").innerHTML = randomStore();


</script>

</body>
</html>

Ответ 7

Извините, это новый ответ на старый вопрос, но это можно сделать более эффективно с помощью карты. То, что вам нужно, это случайный выбор, а не случайный случай. Не повторяющийся случайный случай бессмыслен.

Где _a - коллекция, а r не является частью коллекции, мы получаем случайное значение r:

function aRandom(f){
  var r = Math.random();
  aRandom._a[r] ? aRandom(f) : f(r,aRandom._a[r] = 1);
}
aRandom._a = {};

//usage:
aRandom(function(r){ console.log(r) });

Переопределить aRandom._a, когда браузер становится вялым. Чтобы избежать возможной медлительности, нужно действительно использовать алгоритм генерации UUID с достаточной энтропией, чтобы шансы на повторение были фактически нулевыми, а не грубыми, вытесняющими дифференцируемость. Я выбрал имя функции aRandom, потому что латинский префикс A означает "отсюда". Так как чем больше он используется, тем дальше от случайного выхода. Функция генерирует миллион уникальных значений в 2100 мс на Macbook.

Преимущество вышеупомянутого решения не нужно ограничивать набор. Кроме того, несколько абонентов могут использовать его одновременно и предположить, что их значения отличаются от всех других абонентов. Это удобно для таких вещей, как шумоподавляющие распределения с застрахованным без перекрытий.

Однако он может быть изменен так, чтобы возвращать целые числа, чтобы ограничить использование плунжера до указанной длины:

function aRandom(f,c){
  var r = Math.floor(Math.random()*c);
  aRandom._a[r] ? aRandom(f,c) : f(r,aRandom._a[r] = 1);
}
aRandom._a = {};


//usage:
var len = 10;
var resultset = [];
for(var i =0; i< len; i++){
  aRandom(function(r){ resultset.push(r); }, len);
}
console.log(resultset);

Ответ 8

let arr = [];

do {
  let num = Math.floor(Math.random() * 10 + 1);
  arr.push(num);
  arr = arr.filter((item, index) => {
    return arr.indexOf(item) === index
  });
} while (arr.length < 10);

console.log(arr);

Ответ 9

Вы действительно не хотите потерять случайные числа. Истинно случайные числа должны быть в состоянии повторить.

Поистине случайное число похоже на бросание кубиков. Любое число может появиться дальше.

Перемешанные числа похожи на рисование игральных карт. Каждый номер может появиться только один раз.

То, что вы действительно просите, это перетасовать список чисел, а затем использовать первое количество чисел из перетасованного списка.

Подумайте о том, как составить список номеров в порядке, а затем с помощью генератора случайных чисел случайным образом выбрать номер из копии этого списка. Каждый раз поместите выбранный номер в конец нового списка и удалите его из копии старого списка, сократив этот список. Когда вы закончите, новый список будет содержать перетасованные номера, а копия старого списка будет пустой.

В качестве альтернативы вы можете выбрать номер и использовать его немедленно, сократив копию списка, удалив использованный номер. Поскольку вы удалили номер из списка, он не может появиться снова.