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

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

Шрифт:

-
+

Интервал:

-
+
1 ... 53 54 55 56 57 58 59 60 61 ... 121
Перейти на страницу:
сложная часть — генерация сигналов. Еще раз взглянем на gen_cosine:

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. Мы

1 ... 53 54 55 56 57 58 59 60 61 ... 121
Перейти на страницу:

Комментарии

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

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