stdvector inserting and erasing problem - c++

I need to control the amount to touch points in my application, for this I am using a vector container and my basic setup is this:
//--------------------------------------------------------------
void testApp::touchDown(ofTouchEventArgs &touch){
isTouching = true;
touchPoints.push_back( ofVec2f( touch.x, touch.y ) );
}
//--------------------------------------------------------------
void testApp::touchMoved(ofTouchEventArgs &touch){
for ( int i = 0; i < touchPoints.size(); i++ ) {
if ( touch.id == i ) {
touchPoints[i] = ofVec2f( touch.x, touch.y );
}
}
}
//--------------------------------------------------------------
void testApp::touchUp(ofTouchEventArgs &touch){
isTouching = false;
int i = 0;
for ( vector<ofVec2f>::iterator iter = touchPoints.begin(); iter != touchPoints.end(); ) {
//int i = std::distance( touchPoints.begin(), iter );
cout << "i: " << i << endl;
if ( touch.id == i ) {
iter = touchPoints.erase( iter );
}
i++;
++iter;
}
}
But when I move up a finger the app freezes, so there most be something wrong in the touchUp(), any ideas?

Many things: First off, you cannot modify (erase/insert) a container and expect iterators to remain valid!
Let's see. I want to modify touchMove as well:
void testApp::touchMoved(const ofTouchEventArgs & touch)
{
if (touch.id < touchPoints.size())
touchPoints[touch.id] = ofVec2f(touch.x, touch.y);
}
Next, the big one:
void testApp::touchUp(const ofTouchEventArgs & touch)
{
if (touch.id < touchPoints.size())
touchPoints.erase(touchPoints.begin() + touch.id);
}
Basically touch.id is just the index in the vector, so we can use that directly. To erase an element from the middle, we just call erase on the corresponding iterator. Since vector has random access iterators, we can say begin() + touch.id in constant time.
Update: Actually I think your code is broken: After you erase an element from the vector, the other elements move up, so you will lose the association between touch.id and the container element! What's needed is, you guessed it, an associative container:
struct testApp
{
std::map<int, ofVec2f> touchPoints;
bool isTouching;
void touchDown(const ofTouchEventArgs & touch)
{
isTouching = true;
touchPoints[touch.id] = ofVec2f(touch.x, touch.y);
}
void touchMoved(const ofTouchEventArgs & touch)
{
touchPoints[touch.id] = ofVec2f(touch.x, touch.y);
}
void touchUp(const ofTouchEventArgs & touch)
{
isTouching = false;
touchPoints.erase(touch.id);
}
};

If you've done iter=touchPoints.erase(iter) you shouldn't then do ++iter; you've already moved on to the next item.

Related

How to delete an object from a map which contains a vector as value in C++

I have a map which contains a of vector of type Messages.
std::map<std::string, std::vector<Message>> storage;
class Message has 3 member variables.
class Message
{
private:
std::string msg;
std::string msg_type;
int priority;
}
Now i am trying to delete an object which has priority(say 3) from the map. i am using the following function for it. But it doesn't work.
void deleteByMessagePriority(int priority)
{
if (checkPriorityOfMessage(priority))
{
for (std::map<std::string, std::vector<Message>>::iterator it = storage.begin(); it != storage.end(); it++)
{
std::vector<Message> listOfMsgs = it->second;
for (std::vector<Message>::iterator vec_it = listOfMsgs.begin(); vec_it != listOfMsgs.end(); vec_it++)
//for(int index = 0;index < listOfMsgs.size();index++)
{
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
}
}
}
}
Look carefully at this:
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
}
You're looking at the priority of one message (the one referred to by vec_it), but then what are you deleting if it matches?
Instead of writing your own loop, I'd use erase and std::remove_if to remove all the items you care about in that vector at once.
for (auto & item : storage) {
auto &vec = item.second;
auto start_junk = std::remove_if(
vec.begin(), vec.end(),
[=](Message const &m) { return m.priority == priority; });
vec.erase(start_junk, vec.end());
}
if (vec_it->getPriority() == priority)
{
listOfMsgs.pop_back();
pop_back() removes the last element of the vector which you don't want.You want to check erase
Also remember erase() invalidates the iterators so you need iterator to the next element after a deleted element for which we can fortunately use return value of erase
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it); //returns iterator to the element after vec_it which can also be `listOfMsgs.end()`
std::vector<Message> listOfMsgs = it->second;
.
.
.
listOfMsgs.pop_back();
You're copying the list, only to modify the copy. What you meant is:
std::vector<Message>& listOfMsgs = it->second;
Then you can proceed erasing elements. As Gaurav Sehgal says, use the return value of erase:
std::vector<Message>::iterator vec_it = listOfMsgs.begin();
while (vec_it != listOfMsgs.end())
{
if (vec_it->getPriority() == priority)
{
vec_it = listOfMsgs.erase(vec_it);
}
else
{
++vec_it;
}
}

Reset iterator inside loop with erase method

Here's a little piece of code, which is making us a little mad...
for (vector<QSharedPointer<Mine>>::iterator itMine = _mines.begin();
itMine != _mines.end();) {
auto point2 = shipFire[l].getBulletLine().p2();
if (_mines[pos]->checkCollision(point2)) { // _mines is a Vector<QSharedPointer<Mine>>
Explosion explosionMine(_mines[pos]->point());
_explosions.push_back(explosionMine);
itMine = _mines.erase(itMine);
bulletErased = true;
scoreFrame += 100;
}
else {
++itMine;
pos++;
}
}
Here is the problem, the itMine is erasing two of our vector<..<Mine>> and it's making the program to shutdown unexpectedly
We thought about it, and came up with this : All of our iterators are being invalidated after erasing the one of the Mine, right ?
But we are a little confused about how to change our actual code to fit to the new one ?
The main question is : how do we reset this itr ?
ps : if you have any questions, or if you need a little more of code to understand the logic behind, feel free to ask more question !
Best Regards, and thank you in advance.
A not uncommon solution to this problem is to swap the current position with the back position. The last item can then be removed without invalidating any iterators. See live example.
#include <iostream>
#include <vector>
#include <memory>
int main()
{
struct Mine
{
Mine( int id = 0 ): id( id ) { }
bool check_collision_point( int point ) const { return id % point == 0; }
int id;
};
auto mines = std::vector<std::shared_ptr<Mine>>{ 7 };
for( auto i = 0u; i < mines.size(); ++i )
{
mines[i] = std::make_shared<Mine>( i );
}
// save point outside loop
auto point = 2;
for( auto it = mines.begin(); it != mines.end(); )
{
// don't use mines[pos] in loop
if( (*it)->check_collision_point( point ) ){
std::iter_swap(it, mines.rbegin() );
mines.resize( mines.size() -1 );
}
else
++it;
}
for( auto it = mines.begin(); it != mines.end(); ++it)
std::cout << (*it)->id << " ";
}
Update
for (auto itMine = _mines.begin(); itMine != _mines.end();){
auto point2 = shipFire[l].getBulletLine().p2(); // thomas : -> here we need to do it in the loop, to get every bullet position while looping.
if (_mines[pos]->checkCollision(point2)){
Explosion explosionMine(_mines[pos]->point());
_explosions.push_back(explosionMine);
if (_mines[pos]->checkCollision(point2)){
mines_to_be_deleted.push_back(*itMine);
shipFire.erase(it);
bulletErased = true;
scoreFrame += 100;
qDebug() << "Collision bullet mine";
}
}
//else
++itMine;
pos++;
}
for (auto itrDelete = mines_to_be_deleted.begin(); itrDelete != mines_to_be_deleted.end();)
{
_mines.pop_back(); // Here instead of that, we need to erase the current itr (here is our mistake)
//mines_to_be_deleted.erase(itrDelete);
//_mines.resize(_mines.size());
qDebug() << "mine deleted";
++itrDelete;
}
mines_to_be_deleted.clear();
At this point we came up with this.
We tried everything you told us, but none of them worked, we thinked about swapping the current itr to the end of the vector. Thanks for your help.
For now, the program is running perfectly, but when we hit a Mine with a bullet, a ramdom one disapear.
For those who want to know, we have found the solution.
while(!mines_to_be_deleted.empty()) { // _mine_to_be_deleted is a QList<Sharedpointer<Mine>>
std::remove(begin(_mines),end(_mines),mines_to_be_deleted.front());
_mines.pop_back();
mines_to_be_deleted.pop_front();
}
using std::remove allow us to erase the Mine without creating a malloc()

vector iterator incrementable when erasing element of vector in 2 for loops

I am currently programming a little game for the console with an 2D map. 2 Elements of my game are: destroying fields and an enemy, which spreads in a random direction (its getting bigger). These two "entities" are saved in a structure which contains two vectors (X and Y). I am now trying to erase an element of "_Enemy"(<-private instance of the structure in a class, same as "_DestroyedFields") if you destroy the field where the enemy is.
I tried a lot of different variations to do so and whats giving me the error least is this method (I already searched the internet for a while now an couldn't find a answer to my question):
for (std::vector<int>::iterator itEX = _Enemys.X.begin(), itEY = _Enemys.Y.begin();
itEX != _Enemys.X.end() && itEY != _Enemys.Y.end();
++itEX, ++itEY) {
for (std::vector<int>::iterator itX = _DestroyedFields.X.begin(),
itY = _DestroyedFields.Y.begin();
itX != _DestroyedFields.X.end() && itY != _DestroyedFields.Y.end();
++itX, ++itY) {
if (*itY == *itEY && *itX == *itEX){
itEY = _Enemys.Y.erase(itEY);
itEX = _Enemys.X.erase(itEX);
}
}
}
PS: sorry if my english isn't the best, im german ^^
PSS: if you wanna watch over my whole code, you can find it on Github: https://github.com/Aemmel/ConsoleGame1
After erasing using iterator it, you cannot use it further as it is invalidated. You should use a result of a call to erase which is new, valid iterator.
for( it = v.begin(); it != v.end();)
{
//...
if(...)
{
it = v.erase( it);
}
else
{
++it;
}
...
}
I fixed the bug with first: making a "simple structure"(struct Entity{int X; intY} and then std::vector [insert name here]) and then with adding an break; if the condition is true.
for (Uint itE = 0; itE < _Enemys.size(); ++itE){
for (Uint it = 0; it<_DestroyedFields.size(); ++it){
if (_Enemys.at(itE).Y == _DestroyedFields.at(it).Y
&& _Enemys.at(itE).X == _DestroyedFields.at(it).X){
_Enemys.erase(_Enemys.begin()+itE);
break;
}
}
}
With struct Position {int x; int y;}; and some utility operators,
you may do one of the following: (https://ideone.com/0aiih0)
void filter(std::vector<Position>& positions, const std::vector<Position>& destroyedFields)
{
for (std::vector<Position>::iterator it = positions.begin(); it != positions.end(); ) {
if (std::find(destroyedFields.begin(), destroyedFields.end(), *it) != destroyedFields.end()) {
it = positions.erase(it);
} else {
++it;
}
}
}
Or, if input are sorted, you may use a 'difference':
std::vector<Position> filter2(const std::vector<Position>& positions, const std::vector<Position>& destroyedFields)
{
std::vector<Position> res;
std::set_difference(positions.begin(), positions.end(),
destroyedFields.begin(), destroyedFields.end(),
std::back_inserter(res));
return res;
}

Insert into a vector in alphabetical order

I am trying to insert into this Vector (mCards) in alphabetical order. I can't use sort - it must be inserted in alphabetical order! Right now, I'm inserting in reverse. I can't figure out how to get this to insert in correct alphabetical order!
void Rolodex::Add(Card& card)
{
vector<Card>::iterator temp;
if (mCards.size() != 0)
{
for(vector<Card>::iterator it = mCards.begin(); it != mCards.end(); ++it)
{
Card& currentCard = *it;
temp = it;
int compareResult = currentCard.GetLastName().compare(card.GetLastName());
if (compareResult <= 0)
{
mIteratorNumber = it - mCards.begin();
mCards.insert(temp, card);
return;
}
}
}
else
{
mCards.push_back(card);
for(vector<Card>::iterator it = mCards.begin(); it != mCards.end(); ++it)
{
mIteratorNumber = it - mCards.begin();
}
}
}
If you wants a sorted container, you may instead look at std::map and std::set or their multi variant if you may have duplicated values.
To insert into a sorted vector, the right way to do it is to use std::upper_bound
myCards.insert( std::upper_bound( begin(myCards), end(myCards), card), card );
If the card do not have a valid operator<, use a predicate like this
auto it = std::upper_bound( begin(myCards), end(myCards), card,
[] ( Card const & a, Card const & b ) {
return a.GetLastName().compare(b.GetLastName()) < 0;
} );
myCards.insert( it, card );

Can you remove elements from a std::list while iterating through it?

I've got code that looks like this:
for (std::list<item*>::iterator i=items.begin();i!=items.end();i++)
{
bool isActive = (*i)->update();
//if (!isActive)
// items.remove(*i);
//else
other_code_involving(*i);
}
items.remove_if(CheckItemNotActive);
I'd like remove inactive items immediately after update them, inorder to avoid walking the list again. But if I add the commented-out lines, I get an error when I get to i++: "List iterator not incrementable". I tried some alternates which didn't increment in the for statement, but I couldn't get anything to work.
What's the best way to remove items as you are walking a std::list?
You have to increment the iterator first (with i++) and then remove the previous element (e.g., by using the returned value from i++). You can change the code to a while loop like so:
std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
bool isActive = (*i)->update();
if (!isActive)
{
items.erase(i++); // alternatively, i = items.erase(i);
}
else
{
other_code_involving(*i);
++i;
}
}
You want to do:
i= items.erase(i);
That will correctly update the iterator to point to the location after the iterator you removed.
You need to do the combination of Kristo's answer and MSN's:
// Note: Using the pre-increment operator is preferred for iterators because
// there can be a performance gain.
//
// Note: As long as you are iterating from beginning to end, without inserting
// along the way you can safely save end once; otherwise get it at the
// top of each loop.
std::list< item * >::iterator iter = items.begin();
std::list< item * >::iterator end = items.end();
while (iter != end)
{
item * pItem = *iter;
if (pItem->update() == true)
{
other_code_involving(pItem);
++iter;
}
else
{
// BTW, who is deleting pItem, a.k.a. (*iter)?
iter = items.erase(iter);
}
}
Of course, the most efficient and SuperCool® STL savy thing would be something like this:
// This implementation of update executes other_code_involving(Item *) if
// this instance needs updating.
//
// This method returns true if this still needs future updates.
//
bool Item::update(void)
{
if (m_needsUpdates == true)
{
m_needsUpdates = other_code_involving(this);
}
return (m_needsUpdates);
}
// This call does everything the previous loop did!!! (Including the fact
// that it isn't deleting the items that are erased!)
items.remove_if(std::not1(std::mem_fun(&Item::update)));
I have sumup it, here is the three method with example:
1. using while loop
list<int> lst{4, 1, 2, 3, 5};
auto it = lst.begin();
while (it != lst.end()){
if((*it % 2) == 1){
it = lst.erase(it);// erase and go to next
} else{
++it; // go to next
}
}
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
2. using remove_if member funtion in list:
list<int> lst{4, 1, 2, 3, 5};
lst.remove_if([](int a){return a % 2 == 1;});
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
3. using std::remove_if funtion combining with erase member function:
list<int> lst{4, 1, 2, 3, 5};
lst.erase(std::remove_if(lst.begin(), lst.end(), [](int a){
return a % 2 == 1;
}), lst.end());
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
4. using for loop , should note update the iterator:
list<int> lst{4, 1, 2, 3, 5};
for(auto it = lst.begin(); it != lst.end();++it){
if ((*it % 2) == 1){
it = lst.erase(it); erase and go to next(erase will return the next iterator)
--it; // as it will be add again in for, so we go back one step
}
}
for(auto it:lst)cout<<it<<" ";
cout<<endl; //4 2
Use std::remove_if algorithm.
Edit:
Work with collections should be like:
prepare collection.
process collection.
Life will be easier if you won't mix this steps.
std::remove_if. or list::remove_if ( if you know that you work with list and not with the TCollection )
std::for_each
The alternative for loop version to Kristo's answer.
You lose some efficiency, you go backwards and then forward again when deleting but in exchange for the extra iterator increment you can have the iterator declared in the loop scope and the code looking a bit cleaner. What to choose depends on priorities of the moment.
The answer was totally out of time, I know...
typedef std::list<item*>::iterator item_iterator;
for(item_iterator i = items.begin(); i != items.end(); ++i)
{
bool isActive = (*i)->update();
if (!isActive)
{
items.erase(i--);
}
else
{
other_code_involving(*i);
}
}
Here's an example using a for loop that iterates the list and increments or revalidates the iterator in the event of an item being removed during traversal of the list.
for(auto i = items.begin(); i != items.end();)
{
if(bool isActive = (*i)->update())
{
other_code_involving(*i);
++i;
}
else
{
i = items.erase(i);
}
}
items.remove_if(CheckItemNotActive);
Removal invalidates only the iterators that point to the elements that are removed.
So in this case after removing *i , i is invalidated and you cannot do increment on it.
What you can do is first save the iterator of element that is to be removed , then increment the iterator and then remove the saved one.
If you think of the std::list like a queue, then you can dequeue and enqueue all the items that you want to keep, but only dequeue (and not enqueue) the item you want to remove. Here's an example where I want to remove 5 from a list containing the numbers 1-10...
std::list<int> myList;
int size = myList.size(); // The size needs to be saved to iterate through the whole thing
for (int i = 0; i < size; ++i)
{
int val = myList.back()
myList.pop_back() // dequeue
if (val != 5)
{
myList.push_front(val) // enqueue if not 5
}
}
myList will now only have numbers 1-4 and 6-10.
Iterating backwards avoids the effect of erasing an element on the remaining elements to be traversed:
typedef list<item*> list_t;
for ( list_t::iterator it = items.end() ; it != items.begin() ; ) {
--it;
bool remove = <determine whether to remove>
if ( remove ) {
items.erase( it );
}
}
PS: see this, e.g., regarding backward iteration.
PS2: I did not thoroughly tested if it handles well erasing elements at the ends.
You can write
std::list<item*>::iterator i = items.begin();
while (i != items.end())
{
bool isActive = (*i)->update();
if (!isActive) {
i = items.erase(i);
} else {
other_code_involving(*i);
i++;
}
}
You can write equivalent code with std::list::remove_if, which is less verbose and more explicit
items.remove_if([] (item*i) {
bool isActive = (*i)->update();
if (!isActive)
return true;
other_code_involving(*i);
return false;
});
The std::vector::erase std::remove_if idiom should be used when items is a vector instead of a list to keep compexity at O(n) - or in case you write generic code and items might be a container with no effective way to erase single items (like a vector)
items.erase(std::remove_if(begin(items), end(items), [] (item*i) {
bool isActive = (*i)->update();
if (!isActive)
return true;
other_code_involving(*i);
return false;
}));
do while loop, it's flexable and fast and easy to read and write.
auto textRegion = m_pdfTextRegions.begin();
while(textRegion != m_pdfTextRegions.end())
{
if ((*textRegion)->glyphs.empty())
{
m_pdfTextRegions.erase(textRegion);
textRegion = m_pdfTextRegions.begin();
}
else
textRegion++;
}
I'd like to share my method. This method also allows the insertion of the element to the back of the list during iteration
#include <iostream>
#include <list>
int main(int argc, char **argv) {
std::list<int> d;
for (int i = 0; i < 12; ++i) {
d.push_back(i);
}
auto it = d.begin();
int nelem = d.size(); // number of current elements
for (int ielem = 0; ielem < nelem; ++ielem) {
auto &i = *it;
if (i % 2 == 0) {
it = d.erase(it);
} else {
if (i % 3 == 0) {
d.push_back(3*i);
}
++it;
}
}
for (auto i : d) {
std::cout << i << ", ";
}
std::cout << std::endl;
// result should be: 1, 3, 5, 7, 9, 11, 9, 27,
return 0;
}
I think you have a bug there, I code this way:
for (std::list<CAudioChannel *>::iterator itAudioChannel = audioChannels.begin();
itAudioChannel != audioChannels.end(); )
{
CAudioChannel *audioChannel = *itAudioChannel;
std::list<CAudioChannel *>::iterator itCurrentAudioChannel = itAudioChannel;
itAudioChannel++;
if (audioChannel->destroyMe)
{
audioChannels.erase(itCurrentAudioChannel);
delete audioChannel;
continue;
}
audioChannel->Mix(outBuffer, numSamples);
}