Removing one element of a vector while in a loop - c++

for (Shape *i : shapes) {
for (Shape *j : shapes) {
if (i != j) {
if (check(i,j)){
shapes.erase(remove(shapes.begin(), shapes.end(), i), shapes.end());
this causes an error because it's going to carry on iterating even though i does not exist, my question is how do I cleanly do this? currently I get an error "vector iterator not incrementable"
Can i just exit the second loop and continue in the first one?

You cannot erase elements from a vector when you are iterating it by for range loop, as internally it uses iterators that would be invalidated. This should work:
auto end = shapes.end();
for( auto it = shapes.begin(); it != end; ++it ) {
end = shapes.erase( std::remove_if( std::next( it ), shapes.end(), [it]( Shape *s ) {
return check( *it, s );
}, shapes.end() );
}
Note this code is slightly more effective than yours but it assumes that check( s1, s2 ) == check( s2, s1 ), if you can change your check() function to do strict ordering comparison, rather than equivalence, then you would be able to use std::sort and std::unique which are even more effective.

You can't modify the positioning of your shapes elements while using ranged-based for loops. The range loop uses iterators internally, and erasing vector elements invalidates existing iterators.
Try something more like this instead:
auto iter = shapes.begin();
auto end = shapes.end();
while (iter != end) {
auto iter2 = shapes.begin();
bool erased = false;
while (iter2 != end) {
if ((iter != iter2) && check(*iter, *iter2)) {
iter = shapes.erase(iter);
end = shapes.end();
erased = true;
break;
}
++iter2;
}
if (!erased)
++iter;
}
Alternatively, maybe something more like this would also work:
shapes.erase(
std::remove_if(shapes.begin(), shapes.end(),
[shapes&](Shape *i) {
for (Shape *j : shapes) {
if ((i != j) && check(i, j)) {
return true;
}
}
return false;
}
),
shapes.end()
);

You cannot use a range-for loop in this case. Instead use a standard loop with iterators:
for (auto iter = shapes.begin(); iter != shapes.end(); iter++)

Related

Iterator becomes nvalid

ALL,
std::vector<string>::iterator it;
string orig;
bool found = false;
for( it = vec.begin(); it < vec.end() && !found; it++ )
{
if( ... )
{
found = true;
orig = (*it);
}
}
After I get out of the loop the iterator become invalid even if I have found = true.
How do I keep the iterator? I need it for later processing..
MSVC 2017, Windows 8.1
TIA!!!
You could decrement it in the case you found it, to undo the final it++ that you don't want.
if (found) it--;
Or you could use std::find_if, where ... uses value instead of *it.
auto it = std::find_if(vec.begin(), vec.end(), [](std::string & value) ( return value.find("abc"); });
auto found = it != vec.end();
auto orig = *it;

How to compare iterator value with an integer?

The function removeAll(vector<int>& v, const int& x) intends to remove all elements that equals to int x except the first one.
For example
originally
v = [2,2,3,5,5,6,2,8,6]
after removeAll(v, 2)
the output should be [2,3,5,5,6,8,6]
But my code seems fail to compare if (*it == x), so does anyone know the reason? I didn't find any similar question online.
void removeAll(vector<int>& v, const int& x)
{
for (vector<int>::iterator it = v.begin(); it < v.end(); it++)
{
int count = 0;
if (*it == x && count > 0)
{
v.erase(it);
}
if (*it == x)
{
count++;
}
}
}
What you want is this:
void remove_duplicates_of(std::vector<int>& v, int value)
{
auto it = std::find(v.begin(), v.end(), value);
if (it != v.end())
v.erase(std::remove(std::next(it), v.end(), value), v.end());
}
This finds the first instance of value, and if found, erases every subsequent instance starting at the iterator position immediately past the point of discovery.
There are several problems:
count must be declared outside of the loop, otherwise it's reset to 0 on every iteration.
it must be incremented only when you chose to not erase an element.
When you do erase an element, the return value of erase should be assigned to it. (Failing to do so would formally cause UB, but in practice might not break anything, if you're using std::vector. It's not going to work with for most other containers though.)
Here's the fixed code:
void removeAll(vector<int>& v, const int& x)
{
int count = 0;
for (vector<int>::iterator it = v.begin(); it < v.end();)
{
if (*it == x)
{
count++;
}
if (*it == x && count > 1)
{
it = v.erase(it);
}
else
{
it++;
}
}
}
And here's the same code with minor style improvements:
void removeAll(std::vector<int> &v, int x)
{
std::size_t count = 0;
for (auto it = v.begin(); it < v.end();)
{
if (*it == x && count++ != 0)
it = v.erase(it);
else
it++;
}
}
When you erase an iterator, it gets invalidated. The best way of doing this is with standard algorithms as shown in another answer. The thing is, removing elements from the middle of a vector is expensive. It shifts all the elements after that position by 1 element every time you call it. It's better to shift all the elements in a single iteration and then remove what remains at the end. Removing elements from the end is relatively cheap. Here's roughly how you can do it without standard algorithms:
void remove_duplicates_of(std::vector<int>& v, int x) {
auto it = v.begin();
auto last = v.end();
while (it != last && *it != x) ++it; // find the first one
if (it == last) return; // if we didn't find anything, return
++it; // skip the first
while (it != last && *it != x) ++it; // find the second
if (it == last) return; // if we didn't find anything, return
auto next = it;
while (++it != last) // shift all other elements to front
if (*it != x) *next++ = *it;
// remove the rest
v.erase(next, last);
}

Removing duplicates in multigroup

I'm trying to remove elements that have the same key and value in a multimap. This is my code for now. After deleting the element, I get exception.
multimap<string, CStudent> m_StudentMap;
void removeDuplicates() {
for (auto it1 = m_StudentMap.begin(); it1 != --m_StudentMap.end(); it1++) {
for (auto it2 = next(it1, 1); it2 != m_StudentMap.end(); it2++) {
if (it1->first == it2->first) {
if (it1->second == it2->second) {
m_StudentMap.erase(it2);
}
}
}
}
}
You were nearly right, but the trick with erasing elements in maps while iterating is to capture the new iterator returned by erase. I've also generalised the function so it can be used on an argument rather than being limited to m_StudentMap, and stopped the inner loop as soon as the keys diverge.
template <typename K, typename V>
void removeDuplicates(std::multimap<K, V>& mmap)
{
if (mmap.size() < 2) return;
for (auto it = mmap.begin(); it != prev(mmap.end()); ++it)
for (auto it2 = next(it); it2 != mmap.end() && it2->first == it->first; )
if (it->second == it2->second)
it2 = mmap.erase(it2);
else
++it2;
}
You can see it run / fork it etc. here.

how to find if std::deque holds given object?

I have a container<std::deque<T>> and a const T*ptr which I know points to an object in one of the contained deques. Now, I like to know (1) which deque it comes from and (2) its index in that one. How to get that info?
I'm aware that I can iterate over all objects, but there ought to be a faster solution, ought it not?
Something like (and I havent compiled this but you get the idea):
container<deque<T>> MyCont;
for( auto iter = MyCont.begin(); iter != MyCont.end(); ++iter )
{
auto foundIter = find( *iter.begin(), *iter.end(), MyObject );
if ( foundIter != *iter.end() )
{
dequeIndex = distance( *iter.begin(), foundIter );
containerIndex = distance( MyCont.begin(), iter );
break;
}
}
That's a task for a double iteration:
iterate over container
iterate over current element (a deque) and compare
#
template<typename container> std::pair<container::iterator_t, size_t> FindIndices(const container& c, const container::value_type* x) {
for(auto a = c.begin(); a != c.end(); a++)
for(auto b = a->begin(); b != a->end(); b++)
if(&*b == x)
return std::pair<container::iterator_t, size_t>(a, b-a->begin());
return std::pair<container::iterator_t, size_t>(c.end(), -1);
}
If you can instead store shared_pointers or unique_pointers in your container, you can use a std::map to efficiently retrieve the indices, as long as those don't change.
If only the queue does not change, then there is still some potential for savings.

c++ erase the std::vector.end() while looping over all iterators

I want to erase some iterator from a vector, so this is what I have now.
void function(std::vector <class*> & vector)
{
std::vector <class*>::iterator it;
for(it = vector.begin(); iter != vector.end(); ++iter)
{
if(it->value == 1)
vector.erase(it);
}
Display(vector);
return;
}
Apparently this code gives me an error when the iterator got removed is the last one in the vector, otherwise seems it's working fine. I know it might not be desirable behavior to modify vector inside such a loop, but if I have to do this, what will be the best way?
Thanks.
for (it = vector.begin(); it != vector.end(); )
{
if (it->value == 1)
it = vector.erase(it);
else
++it;
}
But for this case, you should actually just use std::remove_if with an appropriate predicate.
Another approach:
vector.erase( std::remove_if( vector.begin(), vector.end(), boost::bind( &class::value, _1 ) == 1 ), vector.end() );
boost::bind can probably be replaced by std::bind if it's available.
It's bad idea to erase from vector while iterate over it. Simply filter it.
void function(std::vector <class*> & vector)
{
std::vector <class*>::iterator from= vector.begin();
std::vector <class*>::iterator to= from;
for(; from != vector.end(); ++from)
{
if((*from)->value == 1) continue;
// it should be (*from) or use boost::ptr_vector
*(to++)=*from;
}
vector.erase( to, vector.end() );
Display(vector);
return;
}
This is functionality exactly identical as code by Ylisar. IMHO this is best for vector, if you always have something to remove, but if remove is very rare (for whole one vector), use Benjamin Lindley version.
Whatever optimization may be, you may filter only if it you have something to erase:
void function(std::vector <class*> & vector)
{
std::vector <class*>::iterator to= vector.begin();
for(; to != vector.end(); ++to)
{
if((*to)->value == 1)
{
std::vector <class*>::iterator from=to;
for(++from; from != vector.end(); ++from)
{
if((*from)->value == 1) continue;
*(to++)=*from;
}
vector.erase( to, vector.end() );
break;
}
}
Display(vector);
return;
}
If you don't need to preserve order, you may copy from back to minimal copy overheat:
void function(std::vector <class*> & vector)
{
std::vector <class*>::iterator to= vector.begin();
std::vector <class*>::iterator from=vector.end();
if(to == from) return;
for(;;)
{
if((*to)->value == 1) // need skip value from begin
{
for( --from; from != to; --from)
{
if((*from)->value == 1) continue; // need skip value from end
*to=*from;
++to; // copied, move to next
}
}
else
{
++to; //preserved, move to next
}
if(to == from)
{
vector.erase( to, vector.end() );
break;
}
}
Display(vector);
return;
}