What's the best way to iterate over two or more containers simultaneously
Asked Answered
E

12

163

C++11 provides multiple ways to iterate over containers. For example:

Range-based loop

for(auto c : container) fun(c)

std::for_each

for_each(container.begin(),container.end(),fun)

However what is the recommended way to iterate over two (or more) containers of the same size to accomplish something like:

for(unsigned i = 0; i < containerA.size(); ++i) {
  containerA[i] = containerB[i];
}
Eddington answered 23/9, 2012 at 12:13 Comment(6)
what about transform present in #include <algorithm> ?Chops
About the assignment loop: if both are vectors or similar, use containerA = containerB; instead of the loop.Tussock
A similar question: #8511535Teage
Possible duplicate of Sequence-zip function for c++11?Distant
If someone is actually looking for iterating over two containers one after another but in a single loop, have a look at #54336729Leekgreen
Very relevant for scientific computing, for one. It seems like range based for, and iterator based for loops will have to wait for such applications, for sake of code homogeneity.Dalessio
A
82

Rather late to the party. But: I would iterate over indices. But not with the classical for loop but instead with a range-based for loop over the indices:

for(unsigned i : indices(containerA)) {
    containerA[i] = containerB[i];
}

indices is a simple wrapper function which returns a (lazily evaluated) range for the indices. Since the implementation – though simple – is a bit too long to post it here, you can find an implementation on GitHub.

This code is as efficient as using a manual, classical for loop.

If this pattern occurs often in your data, consider using another pattern which zips two sequences and produces a range of tuples, corresponding to the paired elements:

for (auto& [a, b] : zip(containerA, containerB)) {
    a = b;
}

The implementation of zip is left as an exercise for the reader, but it follows easily from the implementation of indices.

(Before C++17 you’d have to write the following instead:)

for (auto&& items : zip(containerA, containerB))
    get<0>(items) = get<1>(items);
Amylum answered 25/9, 2013 at 13:19 Comment(16)
Is there any advantage of your indices implementation in comparison to boost counting_range? One could simply use boost::counting_range(size_t(0), containerA.size())Tagliatelle
@Tagliatelle The biggest difference in this case is syntax: mine is (I claim) objectively better to use in this case. Furthermore, you can specify a step size. See the linked Github page, and in particular the README file, for examples.Amylum
Your idea is very nice and I came up with the use of counting_range only after seeing it: clear upvote :) However, I wonder if it provides additional value to (re-)implement this. E.g., regarding performance. Nicer syntax, I agree, of course, but it would be enough to write a simple generator function to compensate this drawback.Tagliatelle
@Tagliatelle I admit that I when I wrote the code I deemed it simple enough to live in isolation without using a library (and it is!). Now I would probably write it as a wrapper around Boost.Range. That said, the performance of my library is already optimal. What I mean by this is that using my indices implementation yields compiler output which is identical to using manual for loops. There is no overhead whatsoever.Amylum
Since I use boost anyway, it would be simpler in my case. I already wrote this wrapper around boost range: a function with one line of code is all I need. However, I would be interested if the performance of boost ranges is optimal as well.Tagliatelle
About your cpp11 range: Why do you have indices(std::initializer_list<T>&& cont)? Any rvalue should be "gone" by the time the range based for loop is entered.Religieux
@Religieux To be fair the rvalue reference is completely unnecessary here (I’d use pass by value now). But it doesn’t hurt, either: the value of cont isn’t saved anywhere, and isn’t used inside the loop.Amylum
If you ask me, I find the line containerA[i] = containerB[i]; a lot clearer than the line get<0>(items) = get<1>(items). Why would I even want to do away with the explicit index? And, please, don't say "Performance with list containers!": If you care about performance, there won't be any std::list<> in your code anyway.Find
@cmaster It’s completely unrelated to performance but rather to robustness. Your preferred line has two potential off-by-one errors and potentially out-of-range index errors, which would cause UB. This should raise all the alarms. The line with get may not be as pretty but it’s, by contrast, completely safe from UB. It‘s preventing a common cause of hard-to-debug bugs.Amylum
@KonradRudolph ...at the cost of making the code harder to read, which, in turn, is also a major source of bugs. Yes, the zip() does defend against off-by-one errors. However, I prefer the better readability of the explicit index: In my experience, those off-by-one errors are extremely rare as writing correct loop control for just iterating over a container is an absolute no-brainer to me.Find
@cmaster I agree that readability is an important feature of code, and I’d prefer if the tuple-unpacking code were more readable. But it’s important to remember that it’s not a virtue in itself. Rather, readability just a means of making code more correct. Correctness is the ultimate goal, so in a trade-off between a (slight) increase in readability versus an increase in robustness, we should probably choose robustness.Amylum
@cmaster You say “In my experience, those off-by-one errors are extremely rare”. On the contrary: they are a major source of bugs. This is, after all, why so many modern features of programming languages (not just C++) are so eager to eliminate explicit loops and indexed access. It’s precisely to remove this extremely prevalent source of bugs.Amylum
indices only work if you include "range.hpp" at the github to add: template <typename C, typename = typename std::enable_if<traits::has_size<C>::value>> auto indices(C const& cont) -> range_proxy<decltype(cont.size())> { return {0, cont.size()}; }Hexahydrate
error: use of undeclared identifier 'zip' :(Fluorosis
@AtulVaibhav “The implementation of zip is left as an exercise for the reader”Amylum
@Fluorosis You can use range-v3. Example. The zip view will hopefully be added to the standard library in C++23.Starobin
O
46

i wonder why no one mentioned this:

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 the container sizes don't match, then you may need to put each container specific code into its corresponding if block.

Omaromara answered 28/3, 2016 at 10:36 Comment(2)
you can make it a for loop by using: ``` auto itA = vectorA.begin(); auto itB = vectorB.begin(); for (; (itA != vectorA.end()) && (itB != vectorB.end()); (++itA, ++itB)) ``` also just a personal opinion - I'd be tempted to add a precondition/error if their size differs. ```Liggitt
I think this needs AND not OR: while(itA != vectorA.end() && itB != vectorB.end())?Wrapping
V
40

For your specific example, just use

std::copy_n(contB.begin(), contA.size(), contA.begin())

For the more general case, you can use Boost.Iterator's zip_iterator, with a small function to make it usable in range-based for loops. For most cases, this will work:

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";

Live example.

However, for full-blown genericity, you probably want something more like this, which will work correctly for arrays and user-defined types that don't have member begin()/end() but do have begin/end functions in their namespace. Also, this will allow the user to specifically get const access through the zip_c... functions.

And if you're an advocate of nice error messages, like me, then you probably want this, which checks if any temporary containers were passed to any of the zip_... functions, and prints a nice error message if so.

Vasculum answered 23/9, 2012 at 15:4 Comment(9)
Thanks! One question though, why do you use auto&&, what does it mean &&?Eddington
@memecs: I recommend reading through this question, aswell as this answer of mine which kinda explains how the deduction and reference collapsing is done. Note that auto works exactly the same as a template parameter, and T&& in a template is a universal reference as explained in the first link, so auto&& v = 42 will be deduced as int&& and auto&& w = v; will then be deduced as int&. It allows you to match lvalues aswell as rvalues and let both be mutable, without making a copy.Vasculum
@Xeo: But whats the advantage of auto&& over auto& in a foreach loop?Cameo
@ViktorSehr: It allows you to bind to temporary elements, like those produced by zip_range.Vasculum
@Xeo: I still don't get it, will I avoid a copy by using auto&& instead of auto& in this case?Cameo
@ViktorSehr: auto& is an lvalue reference - it simply cannot bind to temporaries. auto&&, on the other hand, can bind to either temporaries or references (lvalues).Vasculum
@Xeo: Aha (I still cant figure why the compiler can't create a temprary like when you write const auto& value = some_func();Cameo
@ViktorSehr: auto& != auto const&. The former only binds to lvalues.Vasculum
@Vasculum All the links to the examples are broken.Mccaskill
H
21

The answer is here!... when C++23 comes.

#include <algorithm>
#include <forward_list>
#include <ranges>
#include <array>
#include <iostream>

int main()
{
    auto foos = std::to_array({ 1, 2, 3, 4, 5  });
    auto woos = std::to_array({ 6, 7, 8, 9, 10 });

    auto fooswoos = std::views::zip(foos,woos);

    for(auto [foo, woo] : fooswoos) {
        woo += foo;
    }
    std::ranges::for_each(woos, [](const auto& e) { std::cout << e << '\n'; });

    return 0;
}

So, what's happening?

We are constructing a special "view". This view allows us to look at containers as if they were other structures without actually doing any copying or anything like that. Using a structured binding we are able to take a reference to each aligning element per iteration and do whatever we want to it (and safely)

Check it out on compiler explorer right now!

Humdinger answered 22/10, 2022 at 11:39 Comment(0)
C
9

There are a lot of ways to do specific things with multiple containers as provided in the algorithm header. For instance, in the example you've given, you could use std::copy instead of an explicit for loop.

On the other hand, there isn't any built-in way to generically iterate multiple containers other than a normal for loop. This isn't surprising because there are a lot of ways to iterate. Think about it: you could iterate through one container with one step, one container with another step; or through one container until it gets to the end then start inserting while you go through to the end of the other container; or one step of the first container for every time you completely go through the other container then start over; or some other pattern; or more than two containers at a time; etc ...

However, if you wanted to make your own "for_each" style function that iterates through two containers only up to the length of the shortest one, you could do something like this:

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;
  Container2::iterator i2;
  for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
    f(i1, i2);
  }
}

Obviously you can make any kind of iterations strategy you want in a similar way.

Of course, you might argue that just doing the inner for loop directly is easier than writing a custom function like this ... and you'd be right, if you are only going to do it one or two times. But the nice thing is that this is very reusable. =)

Coif answered 23/9, 2012 at 14:40 Comment(4)
It seems like you have to declare the iterators before the loop? I tried this: for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2) but the compiler yells. Can anyone explain why this is invalid?Fash
@DavidDoria The first part of the for loop is a single statement. You can't declare two variables of different types in the same statement. Think about why for (int x = 0, y = 0; ... works, but for (int x = 0, double y = 0; ...) does not.Coif
.. you can, however, have std::pair<Container1::iterator, Container2::iterator> its = {c1.begin(), c2.begin()};Demotic
Another thing to note is that this could be easily made variadic with C++14's typename...Coif
J
9

I personally prefer using what is already in the STL (in the <algorithm> header) if possible. std::transform has a signature that can take two input iterators. So at least for the case of two input containers you could do:

std::transform(containerA.begin(), containerA.end(), containerB.begin(), outputContainer.begin(), [&](const auto& first, const auto& second){
    return do_operation(first, second);
});

Note that the outputContainer can also be one of the input containers. But one limitation is that you cannot do a post update operation if you are modifying one of the containers in place.

Jam answered 23/9, 2020 at 21:28 Comment(3)
+1 for using std library! Using std::back_inserter(outputContainer) as third parameter makes life easier.Coati
I don't like this solution, because from a readability standpoint, it looks like you are transforming A into B, or vice versa.Enroll
@WilderField I agree the STL names are not very good and sometimes could even be misleading. But I would say for a C++ programmer it pays to understand / be comfortable with these idiosyncrasies.Jam
V
8

In case when you need to iterate simultaneously over 2 containers only, there is an extended version of standard for_each algorithm in boost range library, e.g:

#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;
}

When you need to handle more than 2 containers in one algorithm, then you need to play with zip.

Vendor answered 29/4, 2014 at 19:1 Comment(1)
Wonderful! How did you find? Seems like it is not documented anywhere.Decedent
H
8

another solution could be capturing a reference of the iterator of the other container in a lambda and using post increment operator on that. for example simple copy would be:

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++; })

inside lambda you can do whatever with ita and then increment it. This easily extends to the multiple containers case.

Hued answered 9/6, 2016 at 20:15 Comment(0)
S
6

A range-library provides this and other very helpful functionality. The following example uses Boost.Range. Eric Niebler's rangev3 should be a good alternative.

#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;
}

C++17 will make this even better with structured bindings:

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;
}
Snowinsummer answered 25/7, 2016 at 14:12 Comment(4)
This program is not compiling with g++ 4.8.0. delme.cxx:15:25: error: no match for 'operator=' (operand types are 'std::tuple<int&, char&>' and 'const boost::tuples::cons<const int&, boost::tuples::cons<const char&, boost::tuples::null_type> >') std::tie(ti,tc) = i; ^Richmound
After changing std::tie to boost:tie, it compiled.Richmound
I get the following compile error for the version with structured binding (using MSVC 19.13.26132.0 and Windows SDK version 10.0.16299.0): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const boost::tuples::cons<const char &,boost::fusion::detail::build_tuple_cons<boost::fusion::single_view_iterator<Sequence,boost::mpl::int_<1>>,Last,true>::type>' (or there is no acceptable conversion)Jam
structured bindings don't seem to work with boost::combine: https://mcmap.net/q/151802/-boost-combine-range-based-for-and-structured-bindings/8414561Suellensuelo
T
5

I'm a bit late too; but you can use this (C-style variadic function):

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);

or this (using a function parameter pack):

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);

or this (using a brace-enclosed initializer list):

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});

or you can join vectors like here: What is the best way to concatenate two vectors? and then iterate over big vector.

Transgression answered 28/7, 2017 at 11:43 Comment(0)
L
1

C++17 Basic "zip" implementation

This variant zips different sequences to a vector of tuple's that can be used in structured bindings.
You can use any amount of sequences.

Very simple and efficient.

#include <iostream>
#include <utility>
#include <algorithm>
#include <cstddef>
#include <tuple>
#include <vector>

#include <string>
#include <array>
#include <map>

template<typename... Container>
auto zip(Container&... containers) noexcept {
    using tuple_type = std::tuple<std::decay_t<decltype(*std::begin(containers))>&...>;
    std::size_t container_size = std::min({ std::size(containers)... });

    std::vector<tuple_type> result;
    result.reserve(container_size);

    auto iterators = std::make_tuple(std::begin(containers)...);
    for (std::size_t i = 0; i < container_size; ++i) {
        std::apply([&result](auto&... it) { 
            result.emplace_back(*it++...);
        }, iterators);
    }

    return result;
}

int main() {
    std::vector v1 = { 1.1, 1.2, 1.3 };
    std::array v2 = { 1, 2, 3 };

    std::map<std::string, int> v3;
    v3["key1"] = 1;
    v3["key2"] = 2;
    v3["key3"] = 3;

    int arr[] = { 3, 2, 1 };

    for (const auto& [val1, val2, val3, val4] : zip(v1, v2, v3, arr)) {
        std::cout << "vector -> " << val1;
        std::cout << "\narray -> " << val2;
        std::cout << "\nmap -> " << val3.first << '|' << val3.second;
        std::cout << "\nC-array -> " << val4;
        std::cout << "\n\n";
    }

    std::cout.flush();

    return 0;
}

Constant approach

If you want something more efficient you can also try this constexpr approach:
(note that the structured binding must always be auto&& to forward the refences correctly)

#include <iostream>
#include <utility>
#include <algorithm>
#include <iterator>
#include <cstddef>
#include <tuple>

#include <vector>
#include <string>
#include <array>
#include <map>

template<typename... Container>
class zippable {
public:
    using tuple_type_t = std::tuple<std::decay_t<decltype(*std::begin(std::declval<Container&>()))>&...>;
    using iterator_tuple_t = std::tuple<decltype(std::begin(std::declval<Container&>()))...>;

    constexpr zippable(Container&... containers) noexcept : 
         iterators_begin(std::make_tuple(std::begin(containers)...)),
         iterators_end(std::make_tuple(std::end(containers)...)) {}

    class iterator {
    public:
        constexpr iterator(const iterator_tuple_t& it) noexcept : iterators(it) {}
        constexpr iterator(iterator_tuple_t&& it) noexcept : iterators(std::move(it)) {}

        constexpr iterator& operator++() {
            std::apply([](auto&... it) {
                ((it++), ...);
            }, iterators);

            return *this;
        }

        constexpr tuple_type_t operator*() const {
            return std::apply([](const auto&... it) -> tuple_type_t {
                return std::forward_as_tuple(*it...);
            }, iterators);
        }

        constexpr bool operator==(const iterator& other) const {
            return iterators == other.iterators;
        }

        constexpr bool operator!=(const iterator& other) const {
            return iterators != other.iterators;
        }
    private:
        iterator_tuple_t iterators;
    };

    constexpr iterator begin() noexcept {
        return iterator(iterators_begin);
    }

    constexpr iterator end() noexcept {
        return iterator(iterators_end);
    }
private:
    iterator_tuple_t iterators_begin;
    iterator_tuple_t iterators_end;
};

int main() {
    std::vector v1 = { 1.1, 1.2, 1.3 };
    std::array v2 = { 1, 2, 3 };

    std::map<std::string, int> v3;
    v3["key1"] = 1;
    v3["key2"] = 2;
    v3["key3"] = 3;

    int arr[] = { 3, 2, 1 };

    for (auto&& [val1, val2, val3, val4] : zippable(v1, v2, v3, arr)) {
        std::cout << "vector -> " << val1;
        std::cout << "\narray -> " << val2;
        std::cout << "\nmap -> " << val3.first << '|' << val3.second;
        std::cout << "\nC-array -> " << val4;
        std::cout << "\n\n";
    }

    std::cout.flush();

    return 0;
}
Lamphere answered 25/10, 2023 at 11:17 Comment(0)
D
0

Here is one variant

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;
        }
    }

Example usage

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);    
    }
Dextrous answered 18/4, 2017 at 7:54 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.