#include
#include
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 as (sig_len); // a для аналогового сигнала
vector 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{cout, " "});
cout << '\n';
copy(begin(ds), end(ds),
ostream_iterator{cout, " "});
cout << '\n';
5. Теперь перейдем к самой ошибке суммы. Используем метод
std::inner_product
, поскольку его можно легко адаптировать для определения разницы между двумя соответствующими элементами вектора сигналов. Он будет итерировать по обоим диапазонам данных, выбирать элементы в соответствующих позициях диапазонов, рассчитывать их разность и складывать результат.
cout << inner_product(begin(as), end(as), begin(ds),
0.0, std::plus{},
[](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
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
. Мы инициализировали его значением 0.0
. Затем алгоритм принимает две бинарные функции, которые называются bin_op1
и bin_op2
.К этому моменту мы понимаем, что данный алгоритм очень похож на
std::accumulate
. Единственное отличие состоит в том, что последний работает только для одного диапазона данных. Если мы заменим выражение bin_op2(*it1,*it2)
на *it
, то перейдем к алгоритму accumulate
. Поэтому можно считать std::inner_product
версией алгоритма std::accumulate
, которая упаковывает пары входных значений.В нашем случае функция-упаковщик выглядит как
pow(a-b,2)
. Для другой функции, bin_op1
, мы выбрали std::plus
, поскольку хотим сложить сразу все значения, возведенные в квадрат. Реализуем отрисовщик множества Мандельброта в ASCII
В 1975 году математик Бенуа Мандельброт (Benoˆt Mandelbrot) придумал термин «фрактал». Это математическое множество с интересными математическими свойствами, которое в конечном счете выглядит как произведение искусства. Фракталы в приближении выглядят так, будто повторяются бесконечно. Одним из самых популярных фракталов является множество Мандельброта, его вы можете увидеть на рис. 6.7.
Рисунок множества Мандельброта можно сгенерировать путем повторения специальной формулы (рис. 6.8).
Переменные
z
и c
являются комплексными числами. Множество Мандельброта содержит все значения c, для которых формула сходится, если они применяются достаточно часто (рис. 6.7). Одни значения сходятся раньше, другие — позже, так что их можно раскрасить разными цветами. Некоторые из них не сходятся вообще — они отмечены черным.В STL предусмотрен полезный класс
std::complex
. Попробуем реализовать эту формулу, не используя явные циклы, только ради того, чтобы получше узнать STL.
Как это делается
В этом примере мы выведем на консоль такое же изображение, которое было показано на рис. 6.7, в формате ASCII.
1. Сначала включим все заголовочные файлы и объявим об использовании пространства имен
std
:
#include
#include
#include
#include
#include
#include
using namespace std;
2. Множество Мандельброта и формула работают с комплексными числами. Поэтому определим псевдоним типа
cmplx
так, что он имеет типаж std::complex, специализированный для значений типа double
:
using cmplx = complex;
3. Вы можете скомпоновать весь код для отрисовки изображения для множества Мандельброта с помощью ASCII примерно за 20 строк кода, но мы реализуем каждый логический шаг отдельно, а затем соберем все воедино. Первый шаг — реализация функции, которая переводит координаты из целых чисел в числа с плавающей точкой. Изначально мы имеем столбцы и строки для всех позиций символов на консоли. Нужно получить координаты с типом
complex
для системы координат множества Мандельброта. Для этого реализуем функцию, которая принимает параметры, описывающие геометрию системы координат пользовательского окна, а также систему, к которой нужно их привести. Эти значения служат для построения лямбда-выражения, которое будет возвращено позднее. Лямбда-выражение принимает координату