C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Шрифт:
Интервал:
static auto gen_cosine (size_t period_len)
{
return [period_len, n{0}] () mutable {
return cos(double(n++) * 2.0 * M_PI / period_len);
};
}
Каждый экземпляр лямбда-выражения представляет собой объект функции, который изменяет свое состояние при каждом вызове. Его состояние описывается переменными period_len и n. Последняя изменяется с каждым вызовом. Сигнал имеет различные значения в разные моменты времени, выражение n++ описывает увеличивающиеся моменты времени. Чтобы получить сам вектор сигнала из выражения, мы создали вспомогательную функцию signal_from_generator:
template <typename F>
static auto signal_from_generator(size_t len, F gen)
{
csignal r (len);
generate(begin(r), end(r), gen);
return r;
}
Эта вспомогательная функция выделяет память для вектора сигнала с заданной длиной и вызывает метод std::generate, что позволяет заполнить точки его графика. Для каждого элемента вектора r он один раз вызывает объект функции gen, который представляет собой самоизменяющийся объект функции; его можно создать с помощью gen_cosine.
К сожалению, способ решения задачи с помощью STL не позволяет сделать код более элегантным. Ситуация может измениться, если библиотека ranges будет включена в клуб STL (надо надеяться, что это случится в C++20).
Определяем ошибку суммы двух векторов
Существует несколько способов определения численной ошибки между целевым и реальным значениями. Измерение разницы между сигналами, состоящими из множества точек графика, обычно подразумевает использование циклов и вычитание соответствующих точек графика и т.д.
Существует простая формула для определения этой ошибки между сигналами a и b (рис. 6.5).
Для каждого значения i мы вычисляем a[i]–b[i], возводим разность в квадрат (таким образом, получаем возможность сравнить положительные и отрицательные значения) и, наконец, складываем эти значения. Опять же мы могли бы просто воспользоваться циклом, но ради интереса сделаем это с помощью алгоритма STL. Плюс данного подхода заключается в том, что мы не зависим от структуры данных. Наш алгоритм будет работать для векторов и для спископодобных структур данных, для которых нельзя выполнить прямое индексирование.
Как это делается
В этом примере мы создадим два сигнала и посчитаем для них ошибку суммы.
1. Как и обычно, сначала приводим выражения include. Затем объявляем об использовании пространства имен std:
#include <iostream>
#include <cmath>
#include <algorithm>
#include <numeric>
#include <vector>
#include <iterator>
using namespace std;
2. Определим ошибку суммы двух сигналов. Таковыми выступят синусоидальная волна и ее копия, только оригинал будет сохранен в векторе, содержащем переменные типа double, а копия — в векторе, включающем переменные типа int. Поскольку копирование значения из переменной типа double в переменную типа int приводит к потере той его части, что стоит после десятичной точки, мы потеряем какие-то данные. Назовем содержащий переменные типа double вектор as, что расшифровывается как analog signal (аналоговый сигнал), а вектор, который содержит значения типа int, — ds, что значит digital signal (цифровой сигнал). Ошибка суммы позднее покажет, насколько велики потери данных.
int main()
{
const size_t sig_len {100};
vector<double> as (sig_len); // a для аналогового сигнала
vector<int> ds (sig_len); // d для цифрового сигнала
3. Чтобы сгенерировать сигнал синусоидальной волны, реализуем небольшое лямбда-выражение, имеющее изменяемое значение счетчика n. Его можно вызывать по мере необходимости, и при каждом вызове оно будет возвращать значение для следующей временной точки синусоидальной волны. Вызов std::generate заполняет вектор сигнала, а вызов std::copy копирует все значения из вектора переменных типа double в вектор переменных типа int.
auto sin_gen ([n{0}] () mutable {
return 5.0 * sin(n++ * 2.0 * M_PI / 100);
});
generate(begin(as), end(as), sin_gen);
copy(begin(as), end(as), begin(ds));
4. Сначала выведем на экран сигналы, чтобы позднее можно было построить для них график:
copy(begin(as), end(as),
ostream_iterator<double>{cout, " "});
cout << 'n';
copy(begin(ds), end(ds),
ostream_iterator<double>{cout, " "});
cout << 'n';
5. Теперь перейдем к самой ошибке суммы. Используем метод std::inner_product, поскольку его можно легко адаптировать для определения разницы между двумя соответствующими элементами вектора сигналов. Он будет итерировать по обоим диапазонам данных, выбирать элементы в соответствующих позициях диапазонов, рассчитывать их разность и складывать результат.
cout << inner_product(begin(as), end(as), begin(ds),
0.0, std::plus<double>{},
[](double a, double b) {
return pow(a - b, 2);
})
<< 'n';
}
6. Компиляция и запуск программы приведут к выводу двух длинных строк, содержащих сигналы, и третьей строки, в которой показывается единственное значение — разность сигналов. Эта разность равна 40.889. Если бы мы рассчитывали ошибку непрерывно, сначала для первой пары элементов, затем — для первых двух пар, затем — для первых трех и т.д., то получили бы накопленную кривую ошибок. Ее можно увидеть на построенном графике (рис. 6.6).
Как это работает
В данном примере мы решили задачу прохода в цикле по двум векторам, получения разности между их соответствующими значениями, возведения их в квадрат и суммирования этих значений с помощью одного вызова std::inner_product. В то же время единственным кодом, который мы написали, стало лямбда-выражение [](double a,double b) { return pow(a-b,2);}, принимающее разность аргументов и возводящее ее в квадрат.
Взглянув на потенциальную реализацию метода std::inner_product, можно увидеть, как и почему это работает:
template<class InIt1, class InIt2, class T, class F, class G>
T inner_product(InIt1 it1, InIt1 end1, InIt2 it2, T val,
F bin_op1, G bin_op2)
{
while (it1 != end1) {
val = bin_op1(val, bin_op2(*it1, *it2));
++it1;
++it2;
}
return value;
}
Алгоритм принимает пару итераторов (начальный и конечный) для первого диапазона данных, а также начальный итератор для второго диапазона. В нашем случае они представляют собой векторы, для которых нужно определить ошибку суммы. Следующий символ — исходное значение val. Мы
Поделиться книгой в соц сетях:
Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!