Canonical way to iterate with random-access iterator increments > 1? - c++

We routinely iterate over containers with range-base for loop, or the pre-C++11 iterator based approach:
for(auto current = container.begin(); current != container.end(); ++current)
It is usually taught as a much preferred way over the "C-ish" iterations based on index increments:
for(auto currentId = 0; currentId != container.size(); ++currentId)
Yet, what if the requirement is to iterate every N th element, for example every third?
An erroneous rewrite of the above approach would give:
// Error
for(auto current = container.begin(); current != container.end(); current += 3)
Now, our programs exhibit undefined behaviour each time (container.size() % 3) != 0.
What would be the canonical C++ way to address such requirement?
Can it be done reasonably with iterator based iteration?
Should we fall-back to indexes (with comparison becoming <instead of !=)?

Related

Looping backwards starting with a found value

Suppose I have a map<int, set<int>> called my_map and I want to start at the element with key value 5 and loop backward until I get to the beginning, and check each set for a certain criterion. (I need to go backward, because I want to break if the criterion is met.)
std::find returns a forward iterator, so the way I thought of to do it is
for (auto rit = std::make_reverse_iterator(++my_map.find(A[i])); rit != my_map.rend(); ++rit) {
//check conditions on rit->second
}
Is that the best way?

How to access a range/interval of keys in an ordered map in C++?

I am trying to write an if-condition where I want to execute code depending on which elements of a map are accessed, e.g. for a map with 100 elements only for elements 26 to 74. But I do not want to address specific keys but rather a certain fraction of the map. Should I do this with the [] operator? I tried
if(map.size()/4 < iterator < map.size()*3/4){}
but this does not work.
Just get iterators to the beginning and end of the range you want to inspect like
auto it = std::next(map.begin(), map.size() / 4);
auto end = std::next(map.begin(), map.size() * 3 / 4);
and then you can iterate that range like
for (; it != end; ++it)
{
// do stuff here
}
You don't even need the end iterator if you want to you a counter. This saves you the cost of advancing the end iterator through the map which could make a difference, especially on larger maps. That would look like
auto it = std::next(map.begin(), map.size() / 4);
auto end = map.size() / 2;
for (size_t counter = 0; counter < end; ++it, ++counter)
{
// do stuff here
}
You cannot efficiently get this information from a std::map iterator, because they are not random access (but only bidirectional). In other words, given a std::map iterator, you can find out how many entries are before and after it only by decrementing/incrementing it until you are at the start/end. If you do this for every entry (e.g. in some <algorithm> function), that's risking a performance bottleneck - which may be acceptable in your situation, but you should be aware of it.
If you can do what NathanOliver suggests, that's great, but there are situations where it might not be that easy (e.g. non-contiguous ranges).

How do i use the for loop for a specified range while using maps in c++

I want to use the for loops with iterators while using maps and want to run it for a specified range not from begin() end end(). I would want to use it for a range like from 3rd element to 5th element
I would want to use it for a range like from 3rd element to 5th
element
Since std::map's iterator is not a RandomAccessIterator, but only a BidirectionalIterator (you cannot write .begin() + 3), you can use std::next for this purpose:
for (auto it = std::next(m.begin(), 2); it != std::next(m.begin(), 5); ++it)
{
// ...
}
And remember - check your ranges to ensure, that you iterate over a valid scope.
This code should be pretty optimal and safe for corner cases:
int count = 0;
for( auto it = m.begin(); it != m.end(); ++it ) {
if( ++count <= 3 ) continue;
if( count > 5 ) break;
// use iterator
}
but the fact you are iterating an std::map this way shows most probably you are using a wrong container (or your logic for 3rd to 5th element is wrong)

C++ understanding iter and erase

I am just looking at a code, and I must say that I don't fully understand it yet.
vector<long>::iterator iter1;
vector<long>::iterator iter2;
while(m_vPitchMarks[0]<=vPitchPeriode[0])
{
iter1 = m_vPitchMarks.begin();
iter2 = vPitchPeriode.begin();
m_vPitchMarks.erase(iter1);
vPitchPeriode.erase(iter2);
if((m_vPitchMarks.size()==0)||(vPitchPeriode.size()==0))
break;
}
I am trying to break it down:
Here we will do a while-statement while the value of the first element of m_vPitchMarks is smaller than the value of the first element of vPitchPeriod.
while(m_vPitchMarks[0]<=vPitchPeriode[0])
{
}
Here we set something like a reference to the first element (element at index [0]) of both vectors.
iter1 = m_vPitchMarks.begin();
iter2 = vPitchPeriode.begin();
Now we erase all elements from m_vPitchMarks<> that have just this value.
For example, if iter1 had a value of 15, all elements in m_vPitchMarks<> that also have the value of 15 will be deleted, and the vector becomes shortened.
m_vPitchMarks.erase(iter1);
vPitchPeriode.erase(iter2);
Is that correct?
Thank you.
Almost as you say this:
while(m_vPitchMarks[0]<=vPitchPeriode[0])
will loop while the first value of m_vPitchMarks is less than or equal to the first value of vPitchPeriode.
Your explanation for this this code:
iter1 = m_vPitchMarks.begin();
iter2 = vPitchPeriode.begin();
m_vPitchMarks.erase(iter1);
vPitchPeriode.erase(iter2);
is not quite right. It simply removes the first value from both the vectors.
If you were using a std::deque or a std::list instead then you would just call pop_front to get the same result.
Then if we're out of values stop looping:
if((m_vPitchMarks.size()==0)||(vPitchPeriode.size()==0))
break;
Seems a particularly inefficient route given erasing an element from the front of a vector requires shifting every element that is left.

std::remove not working correctly, still has extra elements

std::vector<int> v = {1,2,3,4,5};
auto i = std::remove(v.begin(),v.end(),3);
for(auto j = v.begin(); j!= v.end();++j)
std::cout << *j;
Actual output : 12455
Where does extra 5 come from?
Desired output : 1245
How to achieve this?
remove doesnt actually remove the elements
Remove removes from the range [first, last) all elements that are equal to value. That is, remove returns an iterator new_last such that the range [first, new_last) contains no elements equal to value. 1 The iterators in the range [new_last, last) are all still dereferenceable, but the elements that they point to are unspecified. Remove is stable, meaning that the relative order of elements that are not equal to value is unchanged.`
std::remove algorithm works only using a pair of forward iterators and in general knows nothing about the underlying container.
You need to use the erase-remove idiom to actually remove the element i.e combine erase with remove
auto i = std::remove(v.begin(),v.end(),3);
v.erase(i,v.end());
for(auto j = v.begin(); j!= v.end();++j)
std::cout << *j;
Read the documentation for std::remove again.
The function does not remove elements from a container (in fact, it doesn't even know that a container is involved, as it only sees iterators), it merely moves values in a sequence and return a new iterator i such that all the interval [ begin .. i [ contains all non-removed elements in the original order. Elements left over in [ i .. end [ are unspecified, and it is your responsibility to eliminate that interval from a container (if you need it):
auto i = std::remove(...);
v.erase(i,v.end());
The reason why you have an additional 5 is that the typical removal algorithm copies values into holes left by removed values, and since values past the i iterator are never overwritten, they remain the same as in the original sequence. This behavior, however, is not reliable - just eliminate the values past i without reading them.
remove returns the new end. So the fix of your code is this:
std::vector<int> v = {1,2,3,4,5};
auto newEnd = std::remove(v.begin(),v.end(),3);//return value stored in newEnd
for(auto j = v.begin(); j!= newEnd ;++j) //note j!=newEnd
std::cout << *j;
Output:
1245
Check it out yourself : http://www.ideone.com/3AMD9
Actually, std::remove moves at beginning of the range all elements which don't satisfy predicate, AND returns iterator of the new end.
So usage is erase-remove idiom:
v.erase(std::remove(v.begin(), v.end(), 3), v.end());
In c++20, it can be simplified thanks to std::erase:
std::erase(v, 3);
It's seams that you are printing the n+1 position of the vector in the for() statement.
It should be:
for(auto j = v.begin(); j!= v.end();j++)
std::cout << *j;
j++ no ++j