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

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

Шрифт:

-
+

Интервал:

-
+
1 ... 55 56 57 58 59 60 61 62 63 ... 121
Перейти на страницу:
из диапазона 0...(w*h–1). Эти числа можно использовать в качестве входного источника для нашего диапазона данных функции преобразования, который мы инкапсулировали, применяя to_iteration_count.

  vector<int> v (w * h);

  iota(begin(v), end(v), 0);

  transform(begin(v), end(v), begin(v), to_iteration_count);

10. На этом, по сути, все. Теперь у нас есть вектор v, который мы инициализировали одномерными координатами и переписали счетчиком итераций для множества Мандельброта. На его основе можно вывести красивое изображение. Можно сделать окно консоли длиной w символов, чтобы не выводить на экран символ перевода строки. Но мы можем также нестандартно использовать алгоритм std::accumulate, чтобы он добавил разрывы строк за нас. Он применяет бинарную функцию для сокращения диапазона. Предоставим ему бинарную функцию, принимающую итератор вывода (который мы свяжем с терминалом на следующем шаге) и отдельное значение из диапазона. Выведем это значение как символ *, если количество итераций превышает 50. В противном случае выведем пробел. При нахождении в конце строки (поскольку переменная-счетчик n без остатка делится на w) выведем символ разрыва строки:

  auto binfunc ([w, n{0}] (auto output_it, int x) mutable {

    *++output_it = (x > 50 ? '*' : ' ');

    if (++n % w == 0) { ++output_it = 'n'; }

      return output_it;

  });

11. Вызывая функцию std::accumulate для входного диапазона данных вместе с нашей бинарной функцией print и итератором ostream_iterator, можно отправить рассчитанное множество Мандельброта в окно консоли:

  accumulate(begin(v), end(v), ostream_iterator<char>{cout},

             binfunc);

}

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

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

Все расчеты происходят во время вызова std::transform для одномерного массива:

vector<int> v (w * h);

iota(begin(v), end(v), 0);

transform(begin(v), end(v), begin(v), to_iteration_count);

Что же произошло и почему это работает именно так? Функция to_iteration_count, по сути, представляет собой цепочку вызовов от i_to_xy до scale и mandelbrot_iterations. На рис. 6.10 показаны этапы преобразования.

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

Создаем собственный алгоритм split

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

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

Как это делается

В данном примере мы реализуем собственный алгоритм split и проверим его работу, разбив на фрагменты строку-пример.

1. Сначала включим некоторые части библиотеки STL и объявим об использовании пространства имен std:

#include <iostream>

#include <string>

#include <algorithm>

#include <iterator>

#include <list>

using namespace std;

2. Алгоритм, показанный в этом разделе, предназначен для разбиения диапазонов данных. Он принимает начальный и конечный итераторы, а также итератор вывода, что поначалу делает его похожим на алгоритмы std::copy и std::transform. Другими его параметрами являются split_val и bin_func. Параметр split_val — значение, которое мы ищем во входном диапазоне данных; оно представляет собой точку разбиения, по которой мы отделяем входной диапазон данных. Параметр bin_func — функция, преобразующая пару итераторов, которые отмечают начало и конец этого поддиапазона. Мы проитерируем по входному диапазону данных с помощью std::find, так что будем перескакивать между включениями значений split_val. При разбиении отдельной строки на отдельные слова мы станем перескакивать от пробела к пробелу. При нахождении искомого значения останавливаемся, чтобы сформировать фрагмент и передать его в выходной диапазон данных:

template <typename InIt, typename OutIt, typename T, typename F>

InIt split(InIt it, InIt end_it, OutIt out_it, T split_val,

           F bin_func)

{

  while (it != end_it) {

    auto slice_end (find(it, end_it, split_val));

    *out_it++ = bin_func(it, slice_end);

    if (slice_end == end_it) { return end_it; }

    it = next(slice_end);

  }

  return it;

}

3. Воспользуемся новым алгоритмом. Создадим строку, которую затем разобьем на части. Элементом, отмечающим конец последнего фрагмента и начало следующего, будет дефис '-':

int main()

{

  const string s {"a-b-c-d-e-f-g"};

4. Когда алгоритм вызывает функцию bin_func для пары итераторов, мы хотим создать на его основе новую строку:

  auto binfunc ([](auto it_a, auto it_b) {

    return string(it_a, it_b);

  });

5. Выходной диапазон данных представляет собой список строк. Мы вызовем алгоритм split, который спроектирован так, что похож на другие алгоритмы STL:

  list<string> l;

  split(begin(s), end(s), back_inserter(l), '-', binfunc);

6. Чтобы увидеть полученное, выведем на экран новый список разбитых строк:

  copy(begin(l), end(l), ostream_iterator<string>{cout, "n"});

}

7. Компиляция и запуск программы дадут следующий результат. Он не содержит дефисов и показывает, что включает отдельные слова (которые в нашем примере являются отдельными символами):

$ ./split

a

b

c

d

e

f

g

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

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

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

Комментарии

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

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