norman
norman

Reputation: 5556

Sequentially iterate over arbitrary number of vectors in C++

I have a function that gathers and concatenates some number of vectors (with the same types of elements, of course). Here is the bare-bones idea of it:

vector<A> combined;
...
for(int i = 0; i < someNumber; i++)
    combined.insert(combined.end(), GetNextRange().begin(), GetNextRange().end());

All of this is done so that I can sequentially iterate over the combined set. I would like to achieve this without all of the copying business.

Since GetNextRange() actually returns a reference to the next vector in line, how can I make use of that fact and put the references together/line them up for the desired access method?

Upvotes: 3

Views: 2425

Answers (2)

saper0
saper0

Reputation: 374

I wanted to iterate over two vectors sequentially and thought there has to be a more simple answer. My following solution (C++11 implementation, but easily adapted to older versions) - hinted at in the comments on Dietmar Kühls answer which I initially overlooked as they are quite burried - is not as sophisticated but did the trick for my use case:

vector<vector<A>*> v_ps;
for ( int i = 0; i != someNumber; ++i ) {
    v_ps.push_back( &GetNextRange() );
}
for( auto v_p : v_ps ){
    for( A& el : *v_p ){
        // do what you wish with el
    }
}

Note: I assume GetNextRange() returns the next vector to be iterated over

Note II: As it is not as sophisticated, it has drawbacks especially requiring a double for-loop all the time. It is upon you and your application if that is acceptable.

Note III: I leave the possibility for changes on the individual elements intentially open

Upvotes: 0

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153820

First off, if GetNextRange() actually, indeed does return the next vector, your use of GetNextRange() twice to get the begin() and the end() won't do you much good. At the very least you'd need to do something along the lines of

for (int i = 0; i != someNumber; ++i) {
    std::vector<A> const& tmp(GetNextRange());
    combined.insert(combined.end(), tmp.begin(), tmp.end());
}

If you want to make this somewhat nice you can create a custom iterator which internally stores the current vector, the index, and a current position. It will probably be an input iterator actually storing the current information in a suitable shared record so it can be copied.

Here is a simple (and untested) implementation:

class joined_iterator {
    struct record {
        record(int limit)
            : index(0)
            , limit(limit)
            , current(limit? &GetNextRange(): 0)
            , pos()
        {
            if (this->current) {
                this->current->begin();
            }
        }
        int                            index;
        int                            limit;
        std::vector<A> const*          current;
        std::vector<A>::const_iterator pos;
    };
    std::shared_ptr<record> ptr;
public:
    joined_iterator(int limit): ptr(std::make_shared<record>(limit)) {}
    bool operator== (joined_iterator const& other) const {
        return this->ptr->current
            ? bool(other.ptr->current)
            : !bool(other.ptr->current);
    }
    bool operator!= (joined_iterator const& other) const {
        return !(*this == other);
    }
    A const& operator*() const { return *this->ptr->pos; }
    A const* operator->() const { return &*this->ptr->pos; }
    joined_iterator& operator++() {
        if (++this->ptr->pos == this->ptr->current->end()) {
            if (++this->ptr->index == this->ptr->limit) {
                this->ptr->current = 0;
            }
            else {
                this->ptr->current = &GetNextRange();
                this->ptr->pos = this->ptr->current->begin();
            }
        }
        return *this;
    }
    joined_iterator operator++(int) {
        joined_iterator rc(*this);
        this->operator++();
        return rc;
    }
};            

Upvotes: 5

Related Questions