简体   繁体   中英

How to access the position of an object within a vector when using an iterator?

I'm currently doing this:

for (std::vector<myClass>::const_iterator i = myVec.begin(); i != myVec.end(); ++i) {
    std::cout << *i << ", ";
}

As it is the recommended way to output all the members of a vector. The problem is that this means that all of my members of this vector get output on a singular line. I want to have it so that I instead output 5 on each line, so that I don't have to scroll too long. If I didn't use an iterator, so my i was just an int , I could just say if (i%5 == 0) then cout << endl but because it is an iterator, I am confused. I tried varying ways of saying what I want to do, but I can't find an answer... Is this possible with an iterator, or should I just use an int instead..?

Yes, you can do this with iterators:

for (std::vector<myClass>::const_iterator i = myVec.begin(); i != myVec.end(); ++i) {
    std::cout << *i << ", ";
    if (i != myVec.begin() && std::distance(myVec.begin(), i) % 5 == 0)
        std::cout << "\n";
}

In this case, a counting loop would work as well:

for (auto i = 0u; i < myVec.size(); ++i) {
    std::cout << myVec[i] << ", ";
    if (i && i % 5 == 0)
        std::cout << "\n";
}

You could write this with a range-for loop as well:

int i = 0;
for (auto &elem : myVec) {
    std::cout << elem << ", ";
    if (++i % 5 == 0)
        std::cout << "\n";
}

If you use the range-v3 library, you could do:

namespace rs = ranges;
for (auto line : myVec | rs::views::chunk(5)) {
    rs::copy(line, rs::ostream_iterator<myClass>(std::cout, ",");
    std::cout << "\n";
}

or instead:

namespace rv = ranges::views;
for (auto [i, line] : rv::enumerate(myVec)) {
    std::cout << elem << ", ";
    if (i && i % 5 == 0)
        std::cout << "\n";
}

Add int pos = i-myVec.begin() . Then you can use if (pos%5 == 0) as you wanted.

In C++20, you might do:

for (int counter = 0; auto&& e : myVec) {
    std::cout << e << ", ";
    if (++counter == 5) {
        counter = 0;
        std::cout << "\n";
    }
}

Before,

int counter = 0;
for (auto&& e : myVec) {
    std::cout << e << ", ";
    if (++counter == 5) {
        counter = 0;
        std::cout << "\n";
    }
}

You can declare a different variable to use it for counting:

// example vector
vector<int> myVec {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int c = 0;

for (auto i = myVec.begin(); i != myVec.end(); i++) {
    // incrementing the counter and verifying if it gives 0 as the remainder
    if (c++ % 5 == 0)
        cout << endl;

    std::cout << *i << ", ";
}

You'll then get:

1, 2, 3, 4, 5,        // 5 values
6, 7, 8, 9, 10,       // 5 values
11, 12, 13, 14, 15,   // 5 values

Note: You may use auto keyword to reduce such long declaration:

std::vector<myClass>::const_iterator i = myVec.begin();
// better:
auto i = myVec.begin();

As always, there are a lot of possibilities to achieve this. But to answer your initial question:

for (std::vector<myClass>::const_iterator i = myVec.begin(); i != myVec.end(); ++i) {
    std::cout << *i << ", ";

    size_t index = i - myVec.begin();
    if (index % 5 == 0) {
        std::cout << "\n";
    }
}

This prints the value at the iterator position and the index in the vector. You calculate the difference between the begin() of the vector and the current iterator position, which is the index.

While it is possible to do this, I wouldn't recommend it because you have an additional subtraction in every iteration. This is generally not a situation where iterators are very useful, as you've seen for yourself.

If you have access to a sufficiently modern C++ compiler (which I guess you do), you can use the very elegant range-based for-loops :

for (myClass& obj : myVec) {
    std::cout << obj;
}

This "extracts" references to the objects in your vector. Of course, now you're even farther away from the index position because you can't use the iterator-subtraction trick. To solve your problem of keeping an index you can simply keep a separate index counter:

int i = 0;
for (myClass& obj : myVec) {
    std::cout << obj << ", ";
    if (i % 5 == 0) {
        std::cout << "\n";
    }
    i++;
}

Or even use the very new C++20 feature of inline-initialized variables (I don't remember the official name for these):

for (int i = 0; myClass& obj : myVec) {
    std::cout << obj << ", ";
    if (i++ % 5 == 0) {
        std::cout << "\n";
    }
}

Or you can just use the classical for (int i = 0; i < size; i++)... , though this isn't as clear as the range-based for-loop version. With the range-based for-loop it's possible to instantly see that something is done with all objects in the vector.

Just for the fun of it, here is a sample code which allows you to access to both index and value through range-for loop. This has limited value, but shows general approach (RangesTS has this built-in):

#include <vector>
#include <utility>
#include <iostream>

template<class UnderlyingIterator>
struct IndexingIterator {
    using ValueType = std::pair<long long int, const typename UnderlyingIterator::value_type&>;
    IndexingIterator(const UnderlyingIterator it) : underlyingIterator(it) { }

    IndexingIterator& operator++() {
        ++underlyingIterator;
        ++ix;
        return *this;
    }

    bool operator != (const IndexingIterator& rhs) const {
        return underlyingIterator != rhs.underlyingIterator;
    }

    ValueType operator*() const {
        return ValueType{ix, *underlyingIterator};
    }

    long long int index() const { return ix; }

private:
    UnderlyingIterator underlyingIterator;

    long long int ix = 0;
};

template<class Container>
struct Enumeratable {
    using Iterator = typename Container::const_iterator;
    using Indexing = IndexingIterator<Iterator>;

    Enumeratable(const Container& container) : b{container.begin()}, e{container.end()} {}

    Indexing begin() const { return b; }
    Indexing end() const { return e; }

private:
    Indexing b;
    Indexing e;
};

template<class Container>
auto enumerate(const Container& container) {
     return Enumeratable<Container>{container};  
}

void testDriver(const std::vector<int>& vec) {
    for (const auto& [index, value]: enumerate(vec)) {
         std::cout << index << ": " << value << "\n";
    }
 }

 int main() {
     std::vector<int> vec{10, 34, 122, 12};

     testDriver(vec);
     return 0;
 }
if (!myVec.empty())
{
    auto iter = myVec.begin(), end = myVec.end();
    size_t count = 0;
    do {
        std::cout << *iter++;
        if (iter == end) break;
        std::cout << ", ";
        if ((++count % 5) == 0)
            std::cout << '\n';
    }
    while (true);
}

Alternatively, you could write a custom iterator that mimics std::ostream_iterator (or better, infix_iterator ), injecting additional line breaks into the output when needed, eg:

#include <ostream> 
#include <iterator> 

template <class T, 
          class charT = char, 
          class traits = std::char_traits<charT> > 
class my_infix_ostream_iterator : 
    public std::iterator<std::output_iterator_tag, void, void, void, void> 
{ 
    std::basic_ostream<charT, traits> *os;
    charT const* delimiter;
    size_t counter, numPerLine;

public: 
    typedef charT char_type; 
    typedef traits traits_type; 
    typedef std::basic_ostream<charT,traits> ostream_type; 

    my_infix_ostream_iterator(ostream_type& s, size_t numPerLine = 0, charT const *d = 0) 
        : os(&s), delimiter(d), numPerLine(numPerLine), counter(0)
    {} 

    my_infix_ostream_iterator& operator=(T const &item)
    { 
        if (counter > 0)
        {
            if (delimiter)
                *os << delimiter; 
            if ((numPerLine > 0) && ((counter % numPerLine) == 0))
                *os << os->widen('\n');
        }
        *os << item;
        ++counter;
        return *this;
    }

    my_infix_ostream_iterator& operator*() { 
        return *this;
    }

    my_infix_ostream_iterator& operator++() {
        return *this;
    }

    my_infix_ostream_iterator& operator++(int) {
        return *this;
    }
};     

Then you can use it like this:

my_infix_ostream_iterator<myClass> iter(std::cout, 5, ", ");
for (const auto &elem : myVec) {
    *iter++ = elem;
}

or:

std::copy(myVec.begin(), myVec.end(),
    my_infix_ostream_iterator<myClass>(std::cout, 5, ", ")
);

Live Demo

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM