Не погружаясь в объяснения синтаксиса других языков, выделим важную деталь, которая является общей в обоих примерах, — магическую функцию zip
. Что она делает? Принимает два вектора a
и b
и преобразует их в смешанный вектор. Например, при вызове этой функции векторы [a1, a2, a3]
и [b1, b2, b3]
будут выглядеть как [(a1,b1), (a2,b2), (a3,b3)]
. Посмотрите на него внимательно; он работает почти так же, как и ускорители упаковки!
Важное значение имеет тот факт, что теперь вы можете проитерировать по одному объединенному промежутку, выполнив попарное умножение и сложив результаты в переменную-аккумулятор. Именно это и происходит в примерах кода на языках Haskell и Python, где не используются ни циклы, ни ненужные индексные переменные.
Код на языке C++ нельзя сделать таким же элегантным, как код на языке Haskell или Python, но в этом разделе мы поговорим о способах реализации подобных возможностей с помощью итераторов путем добавления
Как это делается
В этом примере мы воссоздадим функцию zip
, известную из языков Haskell и Python. Она будет работать только для векторов, содержащих значения типа double
, чтобы не отвлекаться от механики итераторов.
1. Сначала включим некоторые заголовочные файлы:
#include
#include
#include
2. Далее определим класс zip_iterator
. При переборе диапазонов данных zip_iterator
мы будем получать на каждом этапе пару значений из двух контейнеров. Это значит, что мы итерируем по двум контейнерам одновременно:
class zip_iterator {
3. Итератор-упаковщик должен сохранять два итератора, по одному для каждого контейнера:
using it_type = std::vector
it_type it1;
it_type it2;
4. Конструктор просто сохраняет итераторы обоих контейнеров, по которым нужно проитерировать:
public:
zip_iterator(it_type iterator1, it_type iterator2)
: it1{iterator1}, it2{iterator2}
{}
5. Инкрементирование итератора-упаковщика означает инкрементирование обоих итераторов-членов:
zip_iterator& operator++() {
++it1;
++it2;
return *this;
}
6. Два итератора-упаковщика считаются неравными, если оба их итератора-члена не равны своим коллегам из другого итератора-упаковщика. Обычно вы можете использовать логическое ИЛИ (||)
вместо логического И (&&)
, но представьте, что диапазоны данных имеют неравную длину. В таких случаях нельзя соотнести оба конечных итератора одновременно. Таким образом, можно прервать выполнение цикла при достижении
bool operator!=(const zip_iterator& o) const {
return it1 != o.it1 && it2 != o.it2;
}
7. Оператор сравнения равенства реализуется с помощью другого оператора, изменяя результат его работы на противоположный:
bool operator==(const zip_iterator& o) const {
return !operator!=(o);
}
8. Разыменование итератора-упаковщика открывает доступ к обоим контейнерам в одной и той же позиции:
std::pair
return {*it1, *it2};
}
};
9. Мы рассмотрели код итератора. Нужно сделать итератор совместимым с алгоритмами STL, поэтому следует определить стереотипный код для типажа. По сути, он говорит, что данный итератор является обычным однонаправленным и при разыменовании возвращает пары значений типа double
. Несмотря на то, что мы не использовали в текущем примере difference_type
, для некоторых реализаций STL это может понадобиться для успешной компиляции кода:
namespace std {
template <>
struct iterator_traits
using iterator_category = std::forward_iterator_tag;
using value_type = std::pair
using difference_type = long int;
};
}
10. Следующий шаг — определение класса диапазона данных, функции begin
и end
которого возвращают итераторы-упаковщики:
class zipper {
using vec_type = std::vector
vec_type &vec1
vec_type &vec2
11. Он должен сослаться на два существующих контейнера, чтобы создать итераторы-упаковщики:
public:
zipper(vec_type &va, vec_type &vb)
: vec1{va}, vec2{vb}