📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+
1 ... 91 92 93 94 95 96 97 98 99 ... 121
Перейти на страницу:
счетчик в векторе:

  vector<size_t> v (partitions);

  for (size_t i {0}; i < samples; ++i) {

    ++v[rd() / div];

  }

5. Теперь у нас есть «аккуратная» гистограмма, содержащая значения-примеры. Чтобы вывести ее на экран, нужно получить более подробную информацию о ее реальных значениях счетчика. Извлечем самое большое значение с помощью алгоритма max_element. Затем разделим это значение на 100. Таким образом можно разделить все значения счетчика на max_div и вывести множество звездочек на консоль, не выходя за значение ширины, равное 100. Если самое крупное значение меньше 100 (это может произойти в случае применения небольшого количества образцов), то воспользуемся max для получения минимального делителя 1:

  rand_t max_elm (*max_element(begin(v), end(v)));

  rand_t max_div (max(max_elm / 100, rand_t(1)));

6. Теперь выведем гистограмму на консоль. Каждый сегмент получает собственную строку на консоли. Разделив его значение счетчика на max_div и выведя соответствующее количество символов '*', получаем строки гистограммы, которые помещаются в окно консоли:

  for (size_t i {0}; i < partitions; ++i) {

    cout << setw(2) << i << ": "

         << string(v[i] / max_div, '*') << 'n';

  }

}

7. О’кей, на этом все. Теперь перейдем к основной программе. Позволим пользователю определить, сколько сегментов и образцов следует применить:

int main(int argc, char **argv)

{

  if (argc != 3) {

    cout << "Usage: " << argv[0]

         << " <partitions> <samples>n";

    return 1;

  }

8. Затем считаем эти переменные из командной строки. Конечно, она содержит строки, которые можно преобразовать в числа с помощью функции std::stoull (stoull — это аббревиатура для string to unsigned long long, строки к беззнаковым значениям типа long long):

  size_t partitions {stoull(argv[1])};

  size_t samples {stoull(argv[2])};

9. Теперь вызовем нашу вспомогательную функцию, создающую гистограммы, для каждого генератора случайных чисел, предоставляемого STL. Это сделает наш пример длинным и повторяющим код. Лучше скопируйте пример из Интернета. Интересно взглянуть на результат работы данной программы. Начнем с random_device. Это устройство пытается распределить случайность поровну между всеми возможными значениями:

  cout << "random_device" << 'n';

  histogram<random_device>(partitions, samples);

10. Следующий генератор случайных чисел — это default_random_engine. Тип генератора, на который ссылается данный тип, зависит от конкретной реализации. Он может оказаться одним из следующих генераторов:

  cout << "ndefault_random_engine" << 'n';

  histogram<default_random_engine>(partitions, samples);

11. Затем опробуем ее для всех других генераторов:

  cout << "nminstd_rand0" << 'n';

  histogram<minstd_rand0>(partitions, samples);

  cout << "nminstd_rand" << 'n';

  histogram<minstd_rand>(partitions, samples);

  cout << "nmt19937" << 'n';

  histogram<mt19937>(partitions, samples);

  cout << "nmt19937_64" << 'n';

  histogram<mt19937_64>(partitions, samples);

  cout << "nranlux24_base" << 'n';

  histogram<ranlux24_base>(partitions, samples);

  cout << "nranlux48_base" << 'n';

  histogram<ranlux48_base>(partitions, samples);

  cout << "nranlux24" << 'n';

  histogram<ranlux24>(partitions, samples);

  cout << "nranlux48" << 'n';

  histogram<ranlux48>(partitions, samples);

  cout << "nknuth_b" << 'n';

  histogram<knuth_b>(partitions, samples);

}

12. Компиляция и запуск программы дадут интересные результаты. Мы увидим длинный список, содержащий выходные данные, и узнаем, что все генераторы случайных чисел имеют разные характеристики. Сначала запустим программу, в которой количество сегментов равно 10, а образцов всего 1000 (рис. 8.5).

13. Затем запустим эту же программу снова. В этот раз количество сегментов все еще будет равно 10, но образцов уже 1,000,000. Станет очевидно, что гистограммы выглядят гораздо лучше, когда мы берем для них больше образцов (рис. 8.6). Это важное наблюдение.

Как это работает

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

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

□ чем больше образцов мы возьмем, тем больше будут равны наши счетчики разделов;

□ неравенство счетчиков разделов значительно отличается между отдельными генераторами;

□ для большого числа образцов становится понятно, что производительность отдельных генераторов различается;

□ запустите программу с небольшим количеством образцов несколько раз. Шаблоны распределения будут выглядеть одинаково: генераторы постоянно создают одни и те же последовательности; это говорит о том, что они совсем не случайны. Такие генераторы называются детерминированными, поскольку можно предсказать получаемые значения. Единственным исключением является std::random_device.

Как видите, необходимо рассмотреть несколько характеристик. Для большинства стандартных приложений достаточно применить std::default_random_engine. Эксперты в области криптографии или в других областях, чувствительных к безопасности, будут тщательно выбирать один из доступных генераторов, но для нас, типичных программистов, это не так важно.

Из данного примера следует сделать три вывода.

1. Как правило, std::default_random_engine — это хороший вариант по умолчанию для типичного приложения.

2. Если действительно нужно получить недетерминированные случайные числа, то поможет std::random_device.

3. Можно передать конструктору любого генератора случайных чисел реальное случайное число, полученное от std::random_device (или, например, текущее время на системных часах), чтобы заставить его создавать разные случайные числа при каждом обращении. Это называется посевом.

 

 Обратите внимание: std::random_device может откатиться к одному из детерминированных генераторов, если библиотека не поддерживает недетерминированные генераторы.

Генерируем случайные числа и создаем конкретные распределения с помощью STL

Из предыдущего примера мы узнали о генераторах случайных чисел, предоставляемых STL. Генерация случайных чисел тем или иным способом — зачастую лишь половина работы.

Возникает еще один вопрос: для чего нужны эти числа? Мы просто программно «подбрасываем монетку»? Обычно это делается с помощью конструкции rand()%2, что дает результаты 0 и 1, которые можно сопоставить с орлом и решкой. Справедливо; для этого не нужна библиотека (однако эксперты в области случайных чисел знают, что использование лишь нескольких младших битов случайного числа не позволяет получить хорошую подборку случайных чисел).

Что, если мы хотим смоделировать бросок кубика? Конечно, можно написать код (rand()%6)+1 с целью

1 ... 91 92 93 94 95 96 97 98 99 ... 121
Перейти на страницу:

Комментарии

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

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?