В своих прошлых статьях по изучению C ++ я использовал старые методы C-стиля для генерации случайных чисел. В этой статье я собираюсь представить более современный способ (начиная с C ++ 11) генерировать случайные числа на C ++ с использованием класса random.

Генерация случайных чисел в стиле C

В своих предыдущих статьях по изучению стандартной библиотеки шаблонов C ++ (STL) я использовал старый метод C-стиля генерации случайных чисел. Вот программа, демонстрирующая эту технику:

#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>
using namespace std;
int main () {
  vector<int> numbers;
  srand(time(0));
  for (int i = 1; i <= 10; i++) {
    numbers.push_back(rand() % 100 + 1);
  }
  for (const int n : numbers) {
    cout << n << " ";
  }
  return 0;
}

Результат одного запуска этой программы:

29 8 36 98 93 27 56 91 97 4

Функция rand генерирует случайное число и находится в cstdlib. Функция srand обеспечивает начальное значение для функции rand, чтобы последовательность случайных чисел не повторялась.

Не вдаваясь в подробности (но некоторые из них вы можете увидеть здесь), функция rand менее чем идеальна для генерации случайных чисел. Стандартная библиотека C ++ теперь имеет лучший набор функций для генерации случайных чисел в классе random. В оставшейся части этой статьи мы кратко рассмотрим, как использовать этот класс.

Случайный класс - движки и распределения

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

В стандартной библиотеке C ++ есть 16 механизмов случайных чисел, поэтому я не могу охватить их все в этой статье. Вероятно, вы будете чаще всего использовать default_random_engine, который является зависящим от реализации механизмом для создания случайных значений. Два других двигателя - linear_congruential_engine и mersenne_twister_engine. Пожалуйста, обратитесь к документации стандартной библиотеки C ++ для получения дополнительной информации об этих движках.

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

Использование случайного класса

Теперь давайте посмотрим, как мы можем использовать класс random для генерации случайных чисел, необходимых для наших приложений C ++. В этом первом примере используются default_random_engine и uniform_int_distribution для заполнения целочисленного вектора:

#include <iostream>
#include <vector>
#include <random>
using namespace std;
int main () {
  vector<int> numbers;
  default_random_engine defEngine;
  uniform_int_distribution<int> intDistro(0,100);
  for (int i = 1; i <= 20; i++) {
    numbers.push_back(intDistro(defEngine));
  }
  for (const int n : numbers) {
    cout << n << " ";
  }
  return 0;
}

Вот результат одного запуска этой программы:

0 13 76 46 53 22 4 68 68 94 38 52 83 3 5 53 67 0 38 6

Вы должны отметить здесь пару вещей. Во-первых, когда вы объявляете распределение, вы также указываете диапазон чисел, который должен использовать дистрибутив, как в этой строке кода:

uniform_int_distribution<int> intDistro(0,100);

Затем вы генерируете случайное число, вызывая объект распределения с объектом движка в качестве аргумента, как в этой строке:

numbers.push_back(intDistro(defEngine));

Мы можем увидеть, насколько хорошо работает генератор случайных чисел, очистив вектор и сгенерировав новый набор чисел, как в этом примере:

int main () {
  vector<int> numbers;
  default_random_engine defEngine;
  uniform_int_distribution<int> intDistro(0,100);
  for (int i = 1; i <= 20; i++) {
    numbers.push_back(intDistro(defEngine));
  }
  for (const int n : numbers) {
    cout << n << " ";
  }
  numbers.clear();
  for (int i = 1; i <= 20; i++) {
    numbers.push_back(intDistro(defEngine));
  }
  cout << endl << endl;
  for (const int n : numbers) {
    cout << n << " ";
  }
  return 0;
}

Результат одного запуска этой программы:

0 13 76 46 53 22 4 68 68 94 38 52 83 3 5 53 67 0 38 6
42 69 59 93 85 53 9 66 42 70 91 76 26 4 74 33 63 76 100 36

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

Решение этой проблемы - предоставить начальное число для двигателя при его объявлении. Следующая программа предоставляет такое начальное число, используя функцию time из файла заголовка ctime для возврата системного времени. Вот код:

#include <vector>
#include <random>
#include <ctime>
using namespace std;
int main () {
  vector<int> numbers;
  default_random_engine defEngine(time(0));
  uniform_int_distribution<int> intDistro(0,100);
  for (int i = 1; i <= 20; i++) {
    numbers.push_back(intDistro(defEngine));
  }
  for (const int n : numbers) {
    cout << n << " ";
  }
  return 0;
}

Теперь вы будете получать разные числа при каждом запуске программы.

В предыдущих примерах генерировались целые случайные числа. Мы также можем генерировать случайные числа с плавающей запятой, изменив распределение с uniform_int_distribution на uniform_real_distribution. Вот программа, которая демонстрирует, как генерировать случайные числа с плавающей запятой:

int main () {
  vector<double> numbers;
  default_random_engine defEngine(time(0));
  uniform_real_distribution<double> dblDistro(1.0, 100.0);
  for (int i = 1; i <= 10; i++) {
    numbers.push_back(dblDistro(defEngine));
  }
  for (const double n : numbers) {
    cout << n << " ";
  }
  return 0;
}

Вот результат одного запуска этой программы:

95.9524 32.255 99.9762 35.4064 67.0245 17.2297 92.0428 99.6441 21.8272 34.0419

Примеры здесь не для профессионалов

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

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

Спасибо, что прочитали эту статью, и пишите мне с комментариями и предложениями.