Каков наилучший способ одновременного итерации двух или более контейнеров
С++ 11 предоставляет несколько способов перебора контейнеров. Например:
Цикл на основе диапазона
for(auto c : container) fun(c)
станд:: for_each
for_each(container.begin(),container.end(),fun)
Однако каков рекомендуемый способ перебора двух (или более) контейнеров того же размера, чтобы выполнить что-то вроде:
for(unsigned i = 0; i < containerA.size(); ++i) {
containerA[i] = containerB[i];
}
Ответы
Ответ 1
В вашем конкретном примере просто используйте
std::copy_n(contB.begin(), contA.size(), contA.begin())
Для более общего случая вы можете использовать Boost.Iterator zip_iterator
, с небольшой функцией, чтобы сделать его пригодным для использования в цикле для диапазонов. В большинстве случаев это будет работать:
template<class... Conts>
auto zip_range(Conts&... conts)
-> decltype(boost::make_iterator_range(
boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
boost::make_zip_iterator(boost::make_tuple(conts.end()...))))
{
return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
boost::make_zip_iterator(boost::make_tuple(conts.end()...))};
}
// ...
for(auto&& t : zip_range(contA, contB))
std::cout << t.get<0>() << " : " << t.get<1>() << "\n";
Пример в реальном времени.
Однако для полномасштабной универсальности вы, вероятно, захотите нечто большее, чем this, которое будет корректно работать для массивов и пользовательских типов, которые не имеют члена begin()
/end()
, но имеют функции begin
/end
в своем пространстве имен. Кроме того, это позволит пользователю получить const
доступ через функции zip_c...
.
И если вы сторонник хороших сообщений об ошибках, например я, то вы, вероятно, хотите this, который проверяет, были ли какие-либо временные контейнеры передается какой-либо из функций zip_...
и печатает хорошее сообщение об ошибке, если это так.
Ответ 2
Скорее поздно на вечеринку. Но: я бы перебирал индексы. Но не с классическим циклом for
, а вместо этого с циклом for
на основе диапазона по индексам:
for(unsigned i : indices(containerA))
containerA[i] = containerB[i];
indices
- это простая функция-оболочка, которая возвращает (лениво оцененный) диапазон для индексов. Поскольку реализация - хотя и простая - слишком длинная, чтобы опубликовать ее здесь, вы можете найти реализацию на GitHub.
Этот код эффективен, используя ручную классическую петлю for
.
Если этот шаблон часто встречается в ваших данных, рассмотрите возможность использования другого шаблона, который zip
состоит из двух последовательностей и создает ряд кортежей, соответствующих парным элементам:
for (auto items&& : zip(containerA, containerB))
get<0>(items) = get<1>(items);
Реализация zip
оставлена как упражнение для читателя, но она легко следует из реализации indices
.
Ответ 3
Интересно, почему никто не упомянул об этом:
auto ItA = VectorA.begin();
auto ItB = VectorB.begin();
while(ItA != VectorA.end() || ItB != VectorB.end())
{
if(ItA != VectorA.end())
{
++ItA;
}
if(ItB != VectorB.end())
{
++ItB;
}
}
PS: если размеры контейнера не совпадают, тогда вам придется поместить код внутри операторов if.
Ответ 4
Существует множество способов делать определенные вещи с несколькими контейнерами, как указано в заголовке algorithm
. Например, в примере, который вы указали, вы можете использовать std::copy
вместо явного цикла цикла.
С другой стороны, нет встроенного способа универсального повторения нескольких контейнеров, отличных от обычного цикла. Это не удивительно, потому что есть много способов повторения. Подумайте об этом: вы можете проходить через один контейнер с одним шагом, один контейнер с другим шагом; или через один контейнер, пока он не дойдет до конца, тогда начните вставку, пока вы проходите до конца другого контейнера; или один шаг первого контейнера за каждый раз, когда вы полностью проходите через другой контейнер, а затем начинайте сначала; или какой-либо другой узор; или более двух контейнеров одновременно; и т.д.
Однако, если вы хотите создать свою собственную функцию стиля "for_each", которая выполняет итерацию через два контейнера только до самой короткой, вы можете сделать что-то вроде этого:
template <typename Container1, typename Container2>
void custom_for_each(
Container1 &c1,
Container2 &c2,
std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f)
{
Container1::iterator begin1 = c1.begin();
Container2::iterator begin2 = c2.begin();
Container1::iterator end1 = c1.end();
Container2::iterator end2 = c2.end();
Container1::iterator i1;
Container1::iterator i2;
for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
f(i1, i2);
}
}
Очевидно, что вы можете сделать любую стратегию итераций, которая вам нужна аналогичным образом.
Конечно, вы можете утверждать, что просто делать внутренний цикл for проще, чем писать пользовательскую функцию, подобную этой... и вы будете правы, если вы собираетесь делать это только один или два раза. Но приятно, что это очень многоразово. =)
Ответ 5
Если вам нужно повторить итерацию одновременно только с двумя контейнерами, существует расширенная версия стандартного алгоритма for_each в библиотеке расширенного диапазона, например:
#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>
void foo(int a, int& b)
{
b = a + 1;
}
int main()
{
std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
std::vector<int> contB(contA.size(), 0);
boost::for_each(contA, contB, boost::bind(&foo, _1, _2));
// contB will be now 5,4,6,3
//...
return 0;
}
Когда вам нужно обрабатывать более 2 контейнеров в одном алгоритме, вам нужно играть с zip.
Ответ 6
другое решение может захватывать ссылку итератора другого контейнера в лямбда и с помощью оператора post increment. например, простая копия:
vector<double> a{1, 2, 3};
vector<double> b(3);
auto ita = a.begin();
for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })
внутри lambda вы можете делать все с ita
, а затем увеличивать его. Это легко распространяется на контейнер с несколькими контейнерами.
Ответ 7
Библиотека диапазонов предоставляет эту и другие очень полезные функции. В следующем примере используется Boost.Range. Eric Niebler rangev3 должна быть хорошей альтернативой.
#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>
int main(int, const char*[])
{
std::vector<int> const v{0,1,2,3,4};
std::list<char> const l{'a', 'b', 'c', 'd', 'e'};
for(auto const& i: boost::combine(v, l))
{
int ti;
char tc;
boost::tie(ti,tc) = i;
std::cout << '(' << ti << ',' << tc << ')' << '\n';
}
return 0;
}
С++ 17 сделает это еще лучше со структурированными привязками:
int main(int, const char*[])
{
std::vector<int> const v{0,1,2,3,4};
std::list<char> const l{'a', 'b', 'c', 'd', 'e'};
for(auto const& [ti, tc]: boost::combine(v, l))
{
std::cout << '(' << ti << ',' << tc << ')' << '\n';
}
return 0;
}
Ответ 8
Вот один вариант
template<class ... Iterator>
void increment_dummy(Iterator ... i)
{}
template<class Function,class ... Iterator>
void for_each_combined(size_t N,Function&& fun,Iterator... iter)
{
while(N!=0)
{
fun(*iter...);
increment_dummy(++iter...);
--N;
}
}
Пример использования
void arrays_mix(size_t N,const float* x,const float* y,float* z)
{
for_each_combined(N,[](float x,float y,float& z){z=x+y;},x,y,z);
}
Ответ 9
Я тоже немного опаздываю; но вы можете использовать это (вариационная функция C-стиля):
template<typename T>
void foreach(std::function<void(T)> callback, int count...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
std::vector<T> v = va_arg(args, std::vector<T>);
std::for_each(v.begin(), v.end(), callback);
}
va_end(args);
}
foreach<int>([](const int &i) {
// do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);
или это (используя пакет параметров функции):
template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
std::for_each(v.begin(), v.end(), callback);
}
template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &v, Args... args) {
std::for_each(v.begin(), v.end(), callback);
return foreach(callback, args...);
}
foreach([](const int &i){
// do something here
}, vecA, vecB, vecC, vecD, vecE, vecF);
или это (используя список инициализаторов, заключенный в скобки):
template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> list) {
for (auto &vec : list) {
std::for_each(vec.begin(), vec.end(), callback);
}
}
foreach([](const int &i){
// do something here
}, {vecA, vecB, vecC, vecD, vecE, vecF});
или вы можете присоединиться к векторам, как здесь: Каков наилучший способ конкатенации двух векторов?, а затем перебрать большой вектор.