Подтвердить что ты не робот

Каков наилучший способ одновременного итерации двух или более контейнеров

С++ 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];
}
4b9b3361

Ответ 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});

или вы можете присоединиться к векторам, как здесь: Каков наилучший способ конкатенации двух векторов?, а затем перебрать большой вектор.