How do I walk a bidirectional iterator backwards in clean code? - c++

Suppose I have an std::map and want to do something to the item with key X and every item with a higher key (remember, this is an ordered map). The code is obvious and clear:
auto iter = mymap.find(X);
while (iter != mymap.end()) {
process(*iter);
iter++;
}
or, possibly better, std::for_each(mymap.find(X), mymap.end(), process).
But if my task is to perform processing on the item with key X and every item with a lower key, in that order, I can't find a clean coding pattern that expresses intent.
auto iter mymap.find(x);
if (iter != mymap.end()) {
iter++; // Go forward...
while (iter != mymap.begin()) {
iter--; // ... so we can go backwards
process(*iter);
}
}
If I didn't want to do them in reverse order, it would be easy enough to increment the iterator returned by std::map::find() and then use std::for_each(mymap.begin(), incremented_iter, process).
Bidirectional iterators aren't reverse iterators, so I can't use mymap.rend() as the "off the beginning" to compare against in a while() loop.
Is there a clean way to do this in C++11 (or in +14 or +17, so I'll have something to look forward to)?

You want to process every item with key X or lower, and to do the processing starting at the element with key X and then go backwards. What you could do is use reverse_iterator on the first element past key X and create a reverse iterator, which points to the element just before the one you gave it. Then you can iterate until the "reverse end":
auto pastX = mymap.upper_bound(X); // First element with key > X
// make_reverse_iterator returns iter that dereferences to the previous element
for (auto iter = std::make_reverse_iterator(pastX); iter != mymap.rend(); ++iter)
// Use *iter
For better readability, I usually keep some helper functions to create an "iterable object" (one which can be called with .begin() and .end()) from a pair of iterators, so I would actually write:
auto pastX = mymap.upper_bound(X);
// "IteRange" would return a "range-for-iterable" object from two iterators
for (auto&& elem : IteRange(std::make_reverse_iterator(pastX), mymap.rend()))
// Use elem

Related

Obtaining an iterator from the element passed to a lambda when using std::find_if

I am trying to simplify a recursive function that receives an iterator. Somewhere in the function it is necessary to search for an element matching a given condition in the range going from the iterator to the end of the vector. So, I thought I could use find_if as shown below:
typedef std::vector<Foo> FooVec;
FooVec v;
int f(FooVec::iterator it) {
/* ... */
auto it2 = std::find_if(it, end(v),
[](const Foo& foo) {
auto foo_it = /* obtain the corresponding iterator for foo. */
return f(foo_it) == 0;
});
/* ... */
}
But the lambda function receives an element, not an iterator to the current element, so I cannot easily call f again. I could search for foo in v in order to get the iterator, but that would be inefficient. Alternatively I could just use a regular for loop with the iterators. But I was wondering whether there is the possibility to use find_if in this situation.
Messy, but v.begin() + (&foo - &v.front()) is the iterator pointing to foo. Note that this only works because vector has contiguous storage: don't try it with a list or deque.
If I were you, I would just write the loop myself. (Yeah, I know, I usually say to use the algorithms, but this seems like a case where doing it yourself is easier).
Uncompiled code follows:
for ( auto iter = it; iter != end(v); ++iter )
{
// *iter is the value; iter is the iterator
// if you have to search to the end, you can use [iter, end(v))
}
Works with all containers: vector, list, deque, etc.

Deleting a vector element in a loop, based upon a conditional statement

In this code snippet Update() returns a boolean, if it returns false I would like to delete the element from the vector.
However, this produces a run-time error of debug assertion failed. The expression is "vector iterator not incrementable".
The code:
for(auto iter = someVector.begin(); iter != someVector.end(); ++iter){
if(!iter->get()->Update()) iter = someVector.erase(iter);
}
I have tried subtracting from the iterator as follows too:
for(auto iter = particles.begin(); iter != particles.end(); ++iter){
if(!iter->get()->Update()) iter = --(particles.erase(iter));
}
...but this results in "vector iterator not decrementable".
How can I make my code works as intended; so that the vector element is deleted when the Update() function returns false?
Change the loop to this:
for(auto iter = someVector.begin(); iter != someVector.end();){
if(!iter->get()->Update())
iter = someVector.erase(iter);
else
++it;
}
The reason for the assertion is that, after the call to erase, iter might be equal to end(). The iterator returned by erase is already "next", you're not supposed to increment it.
I'd recommend not using erase() as above in the first place but rather use it something like this:
someVector.erase(std::remove_if(someVector.begin(), someVector.end(),
[](decltype(*someVector.begin()) element){
return !element.get()->update();
},
someVector.end());
When just one element needs to be erased it does roughly the same as using the one iterator version of erase(). When multiple elements need to be erased, it does less copyies/moves. Note that I use a lambda function just because it is easier to express but the same can be done with a suitable function object if lambda functions are not available.

vector iterators incompatible while erase from vector

I have a map which elements are vectors.I have to delete from these vectors all elements which are equal to special number num
std::map<size_t,std::vector<size_t> > myMap;
for (std::map<size_t,std::vector<size_t> >::iterator itMap = myMap.begin();itMap != myMap.end();++itMap )
{
for (std::vector<size_t>::iterator itVec = itMap->second.begin();itVec != itMap->second.end();)
{
auto itNextVec = itVec;
++itNextVec;
if (*itVec == num)
{
itMap->second.erase(itVec );
}
itVec = itNextVec;
}
}
The code causes run-time exepssion .In VS - vector iterators incompatible.
Can someone point what is the cause for that?
Thanks
std::vector::erase returns an iterator to the next position of the list, and so when you do an erase you should make your iterator equal to the returned value.
The only thing that you have to consider is that the returned iterator could be the end so you should check for that.
What I personally like to do is is after doing in an erase and I get the next iterator position, I go back to the previous position of the returned iterator and than call a continue on the for loop
Example:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> myInt;
myInt.push_back(1);myInt.push_back(2);myInt.push_back(3);
for(auto iter = myInt.begin();
iter != myInt.end();
++iter)
{
if(*iter == 1)
{
iter = myInt.erase(iter);
if(iter != myInt.begin())
{
iter = std::prev(iter);
continue;
}
}
std::cout << *iter << std::endl;
}
}
But doing an erase inside of a iterator loop is frowned upon because it invalidates the old iterator and that could cause a lot of issues if you didn't plan for them.
erasing will invalidate the iterator
Iterator validity
Iterators, pointers and references pointing to position (or first) and beyond are
invalidated, with all iterators, pointers and references to elements before position (or
first) are guaranteed to keep referring to the same elements they were referring to
before the call.
You can't trivially erase an item from a collection while iterating over it. Think a little about it, your removing what itVec "points" to, after the removal itVec no longer "points" to an element, so it no longer have a "next" pointer.
If you check e.g. this reference, you will see that the erase function returns an iterator to the next element. Continue the loop with this one (without increasing it of course).
Consider either using a different collection class than vector or creating a new vector with the desired items removed rather than removing from existing vector.

insertion to type map in c++

I dont understand what does that piece of code does
static TwoWayHostPair hostpair;
map <TwoWayHostPair, Traffic> mymap;
//here some map element inserted to mymap and hostpair initialized
map <TwoWayHostPair, Traffic>::iterator iter = mymap.begin();
iter = mymap.find(hostpair);
if (iter == mymap.end()) {
iter = mymap.insert(make_pair(hostPair, Traffic())).first; //line8
}
My question is what happens in line8? I didnt get it. Isnt it supposed to be type map<...>:iterator and after this insertion does it stay same type?
std::map::insert return std::pair< iterator,bool >, below statement is correct. second bool return value indicates whether the insertion took place
iter = mymap.insert(make_pair(hostPair, Traffic())).first; //line8
see reference here
As used here,
iter = mymap.insert(make_pair(hostPair, Traffic())).first; //line8
mymap.insert returns a pair<iterator,bool>. first, then, accesses the iterator.
insert(make_pair......
make_pair is used to insert pair value to a map.You have iterated for all the element in map having hostpair.
EDIT
Refer cplusplus map to know more.
iter = mymap.find(hostpair);
if (iter == mymap.end()) {
iter = mymap.insert(make_pair(hostPair, Traffic())).first; //line8
}
The first line looks for key hostPair in the map, and if it is not found, then it enters the if block where it inserts the key along with its value, and .first returns the iterator to the inserted item.
Improvement
But you can make an improvement to this. You could just write this:
iter = mymap.insert(make_pair(hostPair, Traffic())).first;
which is exactly equivalent to your code. No need to use find and then insert. The result is a performance gain.
If the key already exists, the insert function will NOT insert any item to the map and .first will return you the iterator to the found item. If the key doesn't exist, only then it will insert and .first will return you the iterator to the newly inserted item.
If you want to know whether the key already existed or not, then you can do this:
auto pair = mymap.insert(make_pair(hostPair, Traffic())); //NO .first!
if (pair.second)
{
//a newly created item is inserted
auto iter = pair.first; //iterator to the newly inserted item
}
else
{
//an item with key `hostPair` already exists in the map
auto iter = pair.first; //iterator to the found item
}
Hope that helps.

Peeking the next element in STL container

Is it possible to peek next element in a container which the iterator currently points to without changing the iterator?
For example in std::set,
int myArray[]= {1,2,3,4};
set <int> mySet(myArray, myArray+4);
set <int>::iterator iter = mySet.begin();
//peek the next element in set without changing iterator.
mySet.erase(iter); //erase the element if next element is n+1
C++0x adds a handy utility function, std::next, that copies an iterator, advances it, and returns the advanced iterator. You can easily write your own std::next implementation:
#include <iterator>
template <typename ForwardIt>
ForwardIt next(ForwardIt it,
typename std::iterator_traits<ForwardIt>::difference_type n = 1)
{
std::advance(it, n);
return it;
}
You can use this in your example like so:
if (iter != mySet.end() && next(iter) != mySet.end() && *next(iter) == *iter + 1)
mySet.erase(iter);
Not with iterators in general. An iterator isn't guaranteed to be able to operate non-destructively. The classic example is an Input Iterator that actually represents an underlying input stream.
There's something that works for this kind of iterator, though. A Forward Iterator doesn't invalidate previous copies of itself by the act of moving forward through the collection. Most iterators (including those for STL collections) are at least Forward Iterators, if not a more functional version- only Input Iterators or Output Iterators are more restricted. So you can simply make a copy of your iterator, increment the copy and check that, then go back to your original iterator.
So your peek code:
set <int>::iterator dupe = iter;
++dupe;
// (do stuff with dupe)
set <int>::iterator iter2 = iter;
++iter2;
int peekedValue = *iter2;
You can always make a copy of the iterator and advance the copy:
set <int>::iterator iter = mySet.begin();
set <int>::iterator iterCopy = iter;
iterCopy++;
if (*iterCopy == something)
mySet.erase(iter);
But beware that iterCopy may no longer be valid once you erase iter.
for sequence containers (vector, deque, and list) you can call front which will give you a peek (more info on the lower part of this link).
This will not work for std::set as its nature does not allow for the [] operator, but for containers that do, you can do:
std::vector<int> v;
v.push_back(3);
v.push_back(4);
std::vector<int>::iterator it = v.begin();
std::cout << v[it - v.begin() + 1];
But this could be dangerous if it points to the last element in the container; but the same applies to the solution above. E.g. you'll have to make checks in both cases.