7. Чтобы выполнить линейный перебор всего изображения, напишем еще одну функцию преобразования, которая принимает одномерную координату i
(x, y)
, используя предполагаемую длину строки. После разбиения переменной i
на количество строк и колонок она преобразует их с помощью нашей функции scale
и возвращает комплексную координату: auto i_to_xy ([=](int i) { return scale(i % w, i / w); });
8. Сейчас можно преобразовать одномерные координаты (с типом int
(int, int)
) во множество координат Мандельброта (с типом cmplx
), а затем рассчитать количество итераций (снова тип int
). Объединим все это в одну функцию, которая создаст подобную цепочку вызовов: auto to_iteration_count ([=](int i) {
return mandelbrot_iterations(i_to_xy(i));
});
9. Теперь подготовим все данные. Предположим, что итоговое изображение ASCII имеет w символов в длину и h символов в ширину. Его можно сохранить в одномерном векторе, который имеет w*h
std::iota
значениями из диапазона 0...(w*h–1)
. Эти числа можно использовать в качестве входного источника для нашего диапазона данных функции преобразования, который мы инкапсулировали, применяя to_iteration_count
. vector
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
binfunc);
}
12. Компиляция и запуск программы приводят к следующему результату, который выглядит как изначальное детализированное множество Мандельброта в упрощенной форме (рис. 6.9).
Как это работает
Все расчеты происходят во время вызова std::transform
vector
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