Значительно более изощренную альтернативу rand представляет написанная Джензом Маурером (Jens Maurer) библиотека Boost Random; она была инспирирована предложениями по генерации случайных чисел, представленными в TR1.
TR1 означает «Technical Report One» и представляет собой официальный проект по расширению стандартной библиотеки C++98.
Библиотека Boost Random содержит несколько высококачественных функций по генерации случайных чисел как для целых типов, так и для типов с плавающей точкой, причем с поддержкой многочисленных распределений. Пример 11.12 показывает, как можно сгенерировать случайные числа с плавающей точкой в интервале значений [0,1).
Пример 11.12. Использование библиотеки Boost Random
#include <boost/random.hpp>
#include <iostream>
#include <cstdlib>
using namespace std;
using namespace boost;
typedef boost::mt19937 BaseGenerator;
typedef boost::uniform_real<double> Distribution;
typedef boost::variate_generator<BaseGenerator, Distribution> Generator;
double boostDoubleRand() {
static BaseGenerator base;
static Distribution dist;
static Generator rng(base, dist);
return rng();
}
int main() {
cout << "expect 5 numbers within the interval [0.1)" << endl;
for (int i=0; i < 5; i++) {
cout << boostDoubleRand() << "n";
}
cout << endl;
}
Основное преимущество библиотеки Boost Random в том, что алгоритм генерации псевдослучайных чисел обеспечивает гарантированные и воспроизводимые свойства случайных последовательностей, зависящих от выбранного алгоритма. В примере 11.12 я использую генератор Mersenne Twister (mt19937), потому что он дает хорошее сочетание производительности и качества последовательности случайных чисел.
11.7. Инициализация контейнера случайными числами
Проблема
Требуется заполнить произвольный контейнер случайными числами.
Решение
Можно использовать функции generate и generate_n из заголовочного файла <algorithm> совместно с функтором, возвращающим случайные числа. Пример 11.13 показывает, как это можно сделать.
Пример 11.13. Инициализация контейнеров случайными числами
#include <algorithm>
#include <vector>
#include <iterator>
#include <iostream>
#include <cstdlib>
using namespace std;
struct RndIntGen {
RndIntGen(int l, int h) : low(l), high(h) {}
int operator()() const {
return low + (rand() % ((high - low) + 1));
}
private:
int low;
int high;
};
int main() {
srand(static_cast<unsigned int>(clock()));
vector<mt> v(5);
generate(v.begin(), v.end(), RndIntGen(1, 6));
copy(v.begin(), v.end(), ostream_iterator<int>(cout, "n"));
}
Программа примера 11.13 должна выдать результат, подобный следующему.
3
1
2
6
4
Обсуждение
Стандартная библиотека C++ содержит функции generate и generate_n, специально предназначенные для заполнения контейнеров значениями, полученными функцией генератора случайных чисел. Эти функции принимают нуль-арный функтор (указатель на функцию или объект-функцию без аргументов), результат которого присваивается соседним элементам контейнера. Пример реализации функции generate и generate_n показан в примере 11.14.
Пример 11.14. Пример реализации функций generate и generate_n
template<class Iter_T, class Fxn_T>
void generate(Iter_T first, Iter_T last, Fxn_T f) {
while (first != last) *first++ = f();
}
template<class Iter_T, class Fxn_T>
void generate_n(Iter_T first, int n, Fxn_T f) {
for (int i=0; i < n; ++i) *first++ = f();
}
11.8. Представление динамического числового вектора
Проблема
Требуется иметь тип для манипулирования динамическими числовыми векторами.
Решение
Вы можете использовать шаблон valarray из заголовочного файла <valarray>. Пример 11.15 показывает, как можно использовать шаблон valarray.
Пример 11.15. Применение шаблона valarray
#include <valarray>
#include <iostream>
using namespace std;
int main() {
valarray<int> v(3);
v[0] = 1;
v[1] = 2;
v[2] = 3;
cout << v[0] << ", " << v[1] << ", " << v[2] << endl;
v = v + v;
cout << v[0] << ", " << v[1] << ", " << v[2] << endl;
v /= 2;
cout << v[0] << ", " << v[1] << ", " << v[2] << endl;
}
Программа примера 11.15 выдаст следующий результат.
1, 2, 3
2, 4, 6
1, 2, 3
Обсуждение
Вопреки своему названию тип vector не предназначен для использования в качестве числового вектора, для этой цели используется шаблон valarray. Этот шаблон написан так, чтобы в конкретных реализациях С++, особенно на высокопроизводительных машинах, можно было применить к нему специальную векторную оптимизацию. Другое большое преимущество valarray состоит в наличии многочисленных перегруженных операторов, предназначенных для работы с числовыми векторами. Эти операторы обеспечивают выполнение таких операций, как сложение и скалярное умножение векторов.
Шаблон valarray может также использоваться в стандартных алгоритмах, работающих с массивами, представленными в C-стиле. Пример 11.16 показывает, как можно создавать итераторы, ссылающиеся на начальный элемент valarray и на элемент, следующий за последним.
Пример 11.16. Получение итераторов для valarray
template<class T>
T* valarray_begin(valarray<T>& x) {
return &x[0];
}
template<class T> T* valarray_end(valarray<T>& x) {
return valarray_begin(x) + x.size();
}
Несмотря на немного академичный вид этого примера, не следует пытаться создавать итератор конца valarray, используя выражение &x[х.size()]. Если это сработает, то только случайно, поскольку индексация valarray, выходящая за допустимый индексный диапазон, приводит к непредсказуемому результату.
Отсутствие в valarray функций-членов begin и end, несомненно, противоречит стилю STL. Отсутствие этих функций подчеркивает то, что в valarray реализуется модель, отличная от концепции контейнера STL. Несмотря на это, вы можете использовать valarray в любом обобщенном алгоритме, где требуется итератор с произвольным доступом.
11.9. Представление числового вектора фиксированного размера
Проблема
Требуется иметь эффективное представление числовых векторов фиксированного размера.
Решение
В программном обеспечении обычного типа часто более эффектный результат по сравнению с valarray дает применение специальной реализации вектора, когда его размер заранее известен на этапе компиляции. Пример 11.17 показывает, как можно реализовать шаблон вектора фиксированного размера, названный здесь kvector.
Пример 11.17. kvector.hpp
#include <algorithm>
#include <cassert>
template<class Value_T, unsigned int N>
class kvector {
public:
// открытые поля
Value_T m[N];
// открытые имена, вводимые typedef
typedef Value_T value_type;
typedef Value_T* iterator;
typedef const Value_T* const_iterator;
typedef Value_T& reference;
typedef const Value_T& const_reference;
typedef size_t size_type;
// определение более короткого синонима для kvector
typedef kvector self;
// функции-члены
template<typename Iter_T>
void copy(Iter_T first, Iter_T last) {
copy(first, last, begin());
}
iterator begin() { return m; }
iterator end() { return m + N; }
const_iterator begin() const { return m; }
const_iterator end() const { return m + N; }
reference operator[](size_type n) { return m[n]; }
const_reference operator[](size_type n) const { return m[n]; }
static size_type size() { return N; }
// векторные операции
self& operator+=(const self& x) {
for (int i=0; i<N; ++i) m[i] += x.m[i];
return *this;
}
self& operator-=(const self& x) {
for (int i=0; i<N; ++i) m[i] -= x.m[i];
return *this;
}
// скалярные операции
self& operator=(value_type x) {
std::fill(begin(), end(), x);
return *this;
}
self& operator+=(value_type x) {
for (int i=0; i<N; ++i) m[i] += x;
return *this;
}