Removing elements from a C++ map through a for-loop - c++

My STL is a bit rusty, so forgive me for asking a possibly trivial question. Consider the following piece of code:
map<int,int> m;
...
for (auto itr = m.begin(); itr != m.end(); ++itr) {
if (itr->second == 0) {
m.erase(itr);
}
}
The question is: Is it safe to erase elements while looping over the map?

Yes, but not the way you do it. You're invalidating itr when you erase, then incrementing the invalid iterator.
auto itr = m.begin();
while (itr != m.end()) {
if (itr->first == 0) {
m.erase(itr++);
} else {
++itr;
}
}

I think that you shouldn't use removed iterator at all - in case of lists this causes serious problems, shouldn't be different for maps.
EDIT by Matthieu M: this code is well-formed in C++0x and allowed as an extension by MSVC.
map<int,int> m;
...
auto itr = m.begin();
while (itr != m.end())
{
if (itr->second == 0) {
itr = m.erase(itr);
}
else
{
itr++;
}
}

For the example given, It would actually be easier to use the erase overload that takes a key as an argument. This function erases all elements in the map with the given key (for a map, this is always either zero or one element)
map<int,int> m;
// ...
m.erase(0); // erase all elements with key equivalent to 0

Related

Vector Iterator erase two elements on condition

I'm currently trying to delete 2 elements from a vector if some condition is met. I can successfully remove a single element without the "vector iterator not dereferencable" error occuring, I know the problem is been caused by removing two elements at once which messes up with the Iterators but am unsure as to the correct way of removing more than one element at once.
vector<SomeObj*> objs;
vector<SomeObj*>::iterator it = objs.begin();
while (it != objs.end())
{
vector<SomeObj*>::iterator it2 = objs.begin();
bool deleted = 0;
while (it2 != objs.end())
{
if ((*it)->somecondition(**it2))
{
delete *it2;
*it2 = NULL;
it = objs.erase(it2);
delete *it;
*it = NULL;
it = objs.erase(it); //Will error here due to invalidating the iterator
deleted = 1;
break;
}
++it2;
}
if (!deleted)
++it;
}
The problem is that the first call to erase() might very well invalidate the other iterator. See this post for a quick summary of what gets invalidated when in various containers. I'd say the simplest solution is to first traverse the container and mark the entries to be erased but do not erase them, and then in a second scan just erase everything that was marked. For performance reasons in this second scan you should either use std::remove_if or use reverse iterator.
Working with nested iterators is tricky if you are mutating the container.
I've put together some sample code that does what you are wanting. What I'm doing is delaying the removal by setting the elements to be removed to nullptr and then removing those as we encounter them in the loops.
#include <iostream>
#include <vector>
class Example
{
public:
Example(int size) : size(size) {}
bool somecondition(const Example& other) const
{
return size == other.size;
}
int size;
};
int main()
{
std::vector<Example*> vec;
vec.push_back(new Example(1));
vec.push_back(new Example(2));
vec.push_back(new Example(3));
vec.push_back(new Example(2));
for (auto it1 = vec.begin(); it1 != vec.end();)
{
if (!*it1)
{
it1 = vec.erase(it1);
continue;
}
for (auto it2 = vec.begin(); it2 != vec.end(); ++it2)
{
if (!*it2)
{
vec.erase(it2);
// we need to start the outer loop again since we've invalidated its iterator
it1 = vec.begin();
break;
}
if (it1 != it2 && (*it1)->somecondition(**it2))
{
delete *it1;
*it1 = nullptr;
delete *it2;
*it2 = nullptr;
break;
}
}
++it1;
}
for (auto example : vec)
{
std::cout << example->size << std::endl;
}
return 0;
}

Increment an iterator standard map

ALL,
std::map<int, std::string> addressee;
std::map<int, std::string>::iterator it1, it2;
for( it1 = addressee.begin(); it1 != addressee().end(); it1++ )
{
bool found = false;
for( it2 = it1 + 1; it2 != addressee.end() && !found; it2++ )
{
if( it1->second == it1->second )
{
printf( "Multiple occurences of addressees found" );
found = true;
}
}
}
gcc spits out an error: no match for operator+.
This code is a simplified version of what I'm trying to do right now. I guess I can use std::advance(), but it seems it just going to be a waste of the function call.
Is there a better fix for that?
std::map does not have random access iterators, only bidirectional iterators, so there's no + n operation. Instead, use std::next:
#include <iterator>
#include <map>
// ...
for (auto it1 = addressee.begin(), e = addressee.end(); it1 != e; ++it1)
{
for (auto it2 = std::next(it1); it2 != e; ++it2)
{
if (it1->second == it2->second)
{
// ...
break;
}
}
}
In fact, you should always use std::next, since it knows which iterator category its argument has and what the most efficient way to compute the next iterator is. That way, you don't have to care about the specific container you happen to be using.
#Kerrek has already pointed out how to handle the problem you're having at the syntactic level.
I'm going to consider the problem at a more algorithmic level--what you're really trying to accomplish overall, rather than just looking at how to repair that particular line of the code.
Unless the collection involved is dependably tiny so the efficiency of this operation doesn't matter at all, I'd make a copy of the mapped values from the collection, then use sort and unique on it to see if there are any duplicates:
std::vector<std::string> temp;
std::transform(addressee.begin(), addressee.end(),
std::back_inserter(temp),
[](std::pair<int, std::string> const &in) { return in.second; });
std::sort(temp.begin(), temp.end());
if (std::unique(temp.begin(), temp.end()) != temp.end()) {
std::cout << "Multiple occurrences of addressees found";
found = true;
}
This reduces the complexity from O(N2) to O(N log N), which will typically be quite substantial if the collection is large at all.

Can I reuse an invalidated iterator?

The following code works (and admittedly is not the most efficient way to go about this routine). My question is this, is it discouraged to reuse the iterator as I have done here? Might it produce strange behavior? If so, why?
std::map<char, int> map;
map['a'] = 10;
map['b'] = 30;
map['c'] = 50;
map['d'] = 70;
std::map<char, int>::iterator iterator = map.begin();
for (; iterator != map.end(); iterator++) {
if (iterator->second == 30 || iterator->second == 50) {
map.erase(iterator);
iterator = map.begin();
}
}
No, there is nothing wrong with re-assigning to the iterator and reusing it, because after the assignment operator is run, the old value is completely overwritten.
iterator = map.begin();
You're not using an invalidated iterator, but your logic is flawed. To fix it, make a small change to your code; only increment the iterator if you haven't erased an element during the current iteration. With your current code, assume that the first two elements in the map meet the erasure criterion. Then the second one will be left unerased because you increment past it on the second iteration through the loop.
for (; iterator != map.end();) {
if (iterator->second == 30 || iterator->second == 50) {
map.erase(iterator);
iterator = map.begin();
} else {
++iterator;
}
}
If your compiler supports C++11 you can do this instead to erase elements from the map
for (; iterator != map.end(); ) {
if (iterator->second == 30 || iterator->second == 50) {
iterator = map.erase(iterator);
} else {
++iterator;
}
}

Weird seg fault when erasing from a map

I have the following code:
//update it in the map
std::map<std::string, std::string>::iterator it;
for(it = spreadsheets.at(i).cells.begin(); it != spreadsheets.at(i).cells.end(); ++it)
{
if(it->first == change.first)
{
if(change.second == "")
{
spreadsheets.at(i).cells.erase(change.first);
}
else
{
it->second = change.second;
}
}
}
The code above runs perfectly on my mac however when I run in on a linux computer it throws a seg fault on spreadsheets.at(i).cells.erase(change.first);
Any idea whats causing this error? Ive tried changing erase(change.first) to erase(it) and I am still getting the seg fault.
Because when you erase from the container, your iterator is no longer valid, yet your loop continues.
You could change your loop to:
std::map<std::string, std::string>::iterator it = spreadsheets.at(i).cells.begin();
while (it != spreadsheets.at(i).cells.end())
{
if(it->first == change.first)
{
if(change.second == "")
{
spreadsheets.at(i).cells.erase(it++); //Post increment returns a copy pointing at the current element, while it already points to the next element and thus stays valid after erase
}
else
{
it->second = change.second;
++it;
}
}
else
++it;
}
Now that I think of it, why do you erase with first element of the pair the iterator is pointing to, ie:
spreadsheets.at(i).cells.erase(change.first);
instead of
spreadsheets.at(i).cells.erase(it);
It's less efficient, as another lookup has to be made.
From the documentation of std::map::erase:
References and iterators to the erased elements are invalidated. Other references and iterators are not affected.
Still your loop goes on and you increment your (now invalid) iterator.
Fix: increment your iterator another way, eg.:
std::map<std::string, std::string>::iterator it;
for (it = spreadsheets.at(i).cells.begin(); it != spreadsheets.at(i).cells.end();/*NOTE: no increment here*/)
{
if (it->first == change.first)
{
if (change.second == "")
{
it = spreadsheets.at(i).cells.erase(it); // C++11 only
// in both C++03 and C++11: spreadsheets.at(i).cells.erase(it++);
}
else
{
it->second = change.second;
++it;
}
}
else
++it;
}
Or to avoid confusion because of the numerous execution paths (the very same confusion that made me forget the last else on my first try): just copy the iterator, increment the original one, and then use the copy. This may look overkill in your case but for more complex loops this is sometimes the only way to stay sane. ;)
std::map<std::string, std::string>::iterator it;
for (it = spreadsheets.at(i).cells.begin(); it != spreadsheets.at(i).cells.end();/*NOTE: no increment here*/)
{
std::map<std::string, std::string>::iterator this_it = it++;
if (this_it->first == change.first)
{
if (change.second == "")
{
spreadsheets.at(i).cells.erase(this_it);
}
else
{
this_it->second = change.second;
}
}
}
After element being erased from map pointing to this element will become invalidated. Thus spreadsheets.at(i).cells.erase(change.first); renders it invalid. See Iterator Invalidation Rules
References and iterators to the erased elements are invalidated.
//update it in the map
std::map<std::string, std::string>::iterator it;
for(it = spreadsheets.at(i).cells.begin(); it != spreadsheets.at(i).cells.end(); ++it)
{
if(it->first == change.first)
{
if(change.second == "")
{
spreadsheets.at(i).cells.erase(it--);
}
else
{
it->second = change.second;
}
}
}
At the moment you do spreadsheets.at(i).cells.erase(change.first); the iterator in the std::map (at the current change.first key) is invalidated. So, when you do it++, it is undefined behaviour.
cf Rules for Iterator Invalidation for rules about invalidation of iterators in standard containers
Increment the iterator before you erase it. Why didn't it happen on the Mac? Who knows.. different OS, different behaviour.
SO

How to get unique pairs of values from a stl set

I have a stl set of integers and I would like to iterate through all unique pairs of integer values, where by uniqueness I consider val1,val2 and val2,val1 to be the same and I should only see that combination once.
I have written this in python where I use the index of a list (clusters):
for i in range(len(clusters) - 1):
for j in range(i+1,len(clusters)):
#Do something with clusters[i],clusters[j])
but without an index I am not sure how I can achieve the same thing with a stl set and iterators. I tried out:
for (set<int>::iterator itr = myset.begin(); itr != myset.end()-1; ++itr) {
cout << *itr;
}
but this fails as an iterator doesn't have a - operator.
How can I achieve this, or must I use a different container?
How about something along the following lines:
for(set<int>::const_iterator iter1 = myset.begin(); iter1 != myset.end(); ++iter1) {
for(set<int>::const_iterator iter2 = iter1; ++iter2 != myset.end();) {
{
std::cout << *iter1 << " " << *iter2 << "\n";
}
}
This yields all N*(N-1)/2 unique pairs, where N is the number of integers in your set.
As an aside: use a const_iterator whenever you iterate over a container without modifying anything, it's good style and might have better performance.
EDIT: Modified the code to reflect the suggestion made by Steve Jessop.
You don't need to do end() - 1 since end() is an iterator that points after the last element in the container.
The corrected code is:
for (set<int>::iterator itr = myset.begin(); itr != myset.end(); ++itr) {
for (set<int>::iterator itr2 = itr + 1; itr2 != myset.end(); ++itr2) {
// Do whatever you want with itr and itr2
}
}
Put your data in a boost::bimap, then iterate it both ways, copying the results into a standard STL map which will enforce uniqueness.