Как вы произвольно генерируете X количество чисел, между диапазоном Y и Z в JavaScript?

Например, я хотел бы генерировать 5 уникальных чисел от 1 до 10. Результаты должны быть 5 чисел от 1 до 10 (например, 2 3 4 8 10).

Ответ 1

  • Заполните массив с диапазоном значений
  • Перемешать массив
  • Выберите первые 5 элементов

Если диапазон очень большой, а количество требуемых значений довольно мало (например, 5 различных значений в диапазоне 1... 1000000), то вы можете попытаться генерировать случайные числа в диапазоне и выбросить (маловероятно ) дублирует до тех пор, пока у вас не будет 5. Проблема с подходом "продолжать пытаться" заключается в том, что у вас есть маленький крошечный шанс, что вы можете потратить много времени на строку случайных значений, которые дают вам много дубликатов. Для большого диапазона значений это настолько маловероятно, что, вероятно, стоит рискнуть, если программное обеспечение не дает кислород пациентам с травмой или что-то в этом роде.

Ответ 2

Другой метод - создавать случайные числа и добавлять их только к возвращаемый массив, если он еще не включил их.

function randomRange(from, to, leng){
    var tem, A= [], L= 0, i= 0;
    randomRangeLoop: 
    while(L< leng){
        tem= Math.floor(Math.random()*to)+from;
        i= 0;
        while(i<L){
            if(A[i++]=== tem) continue randomRangeLoop;
        }
        A[L++]= tem;
    }
    return A;
}

alert (randomRange (1, 10, 5))

/* возвращаемое значение: (массив) 8,6,1,3,5 */

Ответ 3

function generateNumbers(resultCount, from, to) {
  var result = []; // holds the final result
  var isAlreadyPicked = []; // quick lookup for the picked numbers
  // holds substitutes for when we pick a number that has been picked before
  var substitutes = []; 

  for (var i = 0; i < resultCount; i++) {
    // pick a random number
    var number = Math.floor(Math.random() * (to - from)) + from;  

    if(isAlreadyPicked[number]) {
      // pick a number from the ones we skipped at the end of the range.
      var j = Math.floor(Math.random() * substitutes.length);
      number = substitutes[j];
      substitutes.splice(j, 1);
    }

    // Save the number and mark it as being picked.
    result.push(number);
    isAlreadyPicked[number] = true;

    // decrease the range. (Because there is 1 less number to choose from)
    to -= 1;

    if(!isAlreadyPicked[to]) {
      // Save a substitute for when we pick the same number in a next iteration.
      substitutes.push(to);
    }    
  }

  return result;
}

Он работает, уменьшая верхнюю часть диапазона с каждой итерацией. Если число, которое было выбрано в предыдущей итерации, уже существует в результате, просто измените его на один из верхних чисел, которые мы оставили вне диапазона (там всегда будет только 1 номер, который ранее не был выбран). Это также истинное случайное число, потому что оно фактически существовало на месте числа, которое было выбрано в одной из предыдущих итераций.

По-моему, это лучший   решение, потому что:

  • Он не выделяет много памяти путем присвоения всех чисел массиву и перетасовка этого массива.

  • У него есть только итерации X, поэтому подключите его до этой дыхательной машины:)

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

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

EDIT 2: В случае, если вам интересно: здесь код, который я использовал для тестирования:

function test(cases, size) {
  var errors = 0;
  for (var i = 0; i < cases; i++) {      
    var start = Math.floor(Math.random() * size);
    var end = start + Math.floor(Math.random() * size);
    var count = Math.floor(Math.random() * (end - start));

    var nrs = generateNumbers(count, start, end);

    console.log('testing', start, end, count, nrs);

    test:
    for (var j = 0; j < count; j++) {
      var testedNumber = nrs[j];
      if(testedNumber < start || testedNumber >= end) {
        console.error('out of range', testedNumber);
        errors += 1;
        break test;
      }
      for (var k = 0; k < count; k++) {
        if(j !== k && nrs[k] === testedNumber) {
          console.error('picked twice', testedNumber);
          errors += 1;
          break test;
        }
      }
    }
  }

  console.log('all tests finished | errors:', errors)
}
test(1000, 20);

РЕДАКТИРОВАТЬ 3:. Pointy предложил использовать более быстрый алгоритм для поиска, является ли число уникальным. Я обновил образец своим предложением. Спасибо, Уайты!

EDIT 4: Сделал алгоритм более эффективным, удалив внутренний цикл и заменив его массивом подстановок. Забавно, что этот алгоритм фактически можно использовать в качестве алгоритма перетасовки в предыдущем ответе:)

Этот вопрос меня очень интригует. У меня уже был такой алгоритм, как раньше. На самом деле это первый раз, когда я нашел решение, которое меня удовлетворяет. Я провел некоторое исследование по этому вопросу, и это похоже на вариант алгоритма Fisher-Yates shuffle.

EDIT 5: Изменен алгоритм выбора случайного числа из заменителей вместо первого.