I am trying to clean up some loops, and move from while and if/else into std::remove_if and have never done this before, can anyone show me how to turn the below into a remove_if that will achieve the same results? Ideally I want to eliminate the if(obj->IsQueuedForRemoval).
Thank you in advance!
Initial:
void ObjectCollection::ProcessRemovals()
{
bool removed = false;
auto objIterator = objects.begin();
while (objIterator != objects.end())
{
auto obj = *objIterator;
if (obj->IsQueuedForRemoval())
{
objIterator = objects.erase(objIterator);
removed = true;
}
else
{
++objIterator;
}
}
if (removed)
{
drawables.ProcessRemovals();
collidables.ProcessRemovals();
}
}
The part I'm getting tripped up on is how I can keep the bool, trigger it if it removes anything and only go to drawables and collidables process removals if it was removed from there.
std::remove_if returns an iterator to end of the range of not removed elements. You can split the erase-remove into two steps:
auto it = std::remove_if(objects.begin(), objects.end(),predicate)
bool removed = (it != objects.end());
if (removed) {
objects.erase(it,objects.end());
drawables.ProcessRemovals();
collidables.ProcessRemovals();
}
Where predicate can be a lambda that checks if the object is queued for removal (assuming IsQueuedForRemoval is const):
auto predicate = [](const Object& obj){ return obj.IsQueuedForRemoval();};
I'll leave it to you to adjust the lambda in case the containers elements type is something else than Object.
Related
I'm new to C++ so I'm having trouble figuring out how to best remove an object from a vector while still iterating through it.
Basically, I need to iterate through two vectors. For every item, if the ID's match, I can remove them.
//For every person, check to see if the available bags match:
for(std::vector<Person>::iterator pit = waitingPeopleVector.begin(); pit != waitingPeopleVector.end(); ++pit) {
for(std::vector<Bag>::iterator bit = waitingBagsVector.begin(); bit != waitingBagsVector.end(); ++bit) {
int pId = pit->getId();
int bId = bit->getId();
if(pId == bId){
//a match occurs, remove the bag and person
}
}
}
Working with iterators is a bit confusing, I know I can use the .erase() function on my vectors, but I can't really pass pit or bit. Any help appreciated. Thanks
From the standard:
The iterator returned from a.erase(q) points to the element
immediately following q prior to the element being erased. If no such
element exists, a.end() is returned.
I would use it in something like using the erase method:
std::vector<Person>::iterator pit = waitingPeopleVector.begin();
std::vector<Bag>::iterator bit = waitingBagsVector.begin();
while (pit != waitingPeopleVector.end())
{
bool didit;
while (bit != waitingBagsVector.end())
{
didit = false;
if (pit->getId() == bit->getId() && !didit)
{
bit = waitingBagsVector.erase(bit);
pit = waitingPeopleVector.erase(pit);
didit = true;
}
else
{
++bit;
}
}
if (didit)
continue;
else
++pit;
}
Using the erase-remove idiom will achieve this objective, the below offers an (untested) way using lambdas and <algorithm> functions to remove elements from wPL which have the same ID as wBL. It shouldn't be too much effort to extend this to both lists. Note, we have used std::list instead of std::vector for faster removal.
std::list<Person> wPL;
std::list<Bag> wBL;
//...
wPL.erase(std::remove_if(wPL.begin(), wPL.end(),
[&wBL](auto x) { return std::find_if(wBL.begin(), wBL.end(), [](auto y)
{ return x.getId() == y.getId();); }), wPL.end() };
I'm writing the Contra Game by Directx9 and c++
please help me about list of bullets
i'm trying below code but it's error: vector intertor incompatible
std::vector<Bullet*> bullets
if (mKeyboard->IsKeyPress(DIK_X))
{
Bullet* toShoot = new Bullet(noneType, _position.x, _position.y, RIGHT);
toShoot->Init();
bullets.push_back(toShoot);
}
Update Funtion:
std::vector<Bullet*>::iterator it = bullets.begin();
while ((it) != bullets.end())
{
(*it)->Update(gameTime, c);
if ((*it)->IsLive() == false)
{
bullets.erase(it++);
}
}
Render funtion
std::vector<Bullet*>::iterator it = bullets.begin();
while (it != bullets.end())
{
if ((*it)->IsLive())
{
(*it++)->Render(gr, cx, cy);
}
}
You can't just increment an iterator passed to erase(…). Do this instead:
if (!(*it)->IsLive()) {
it = bullets.erase(it);
} else {
++it;
}
Your Render function has a different bug. It gets stuck on the first non-live bullet, since the increment is inside the if-block. This is one reason for(…) is usually preferable to while(…):
for (auto it = bullets.begin(); it != bullets.end(); ++it) {
if (…) {
…
}
}
In fact, the Update function should be likewise changed, but omit the ++it.
In your Update function, you are calling the erase while iterating over a vector. The problem is that your it iterator can get invalidated if it is being used in the loop at the same time you're erasing from the vector.
The alternative is to use the erase/remove_if idiom:
#include <algorithm>
//...
bullets.erase(std::remove_if(bullets.begin(), bullets.end(),
[&](Bullet& b) { b.Update(gameTime, c); return !b.IsLive(); }),
bullets.end());
The algorithm function remove_if() is called to determine which bullets will be removed by the erase() function. Note that the lambda includes the logic you used to determine if a bullet should be removed.
I made a game, rambo shoots bullets and bullets hit zombies and I want to erase the zombie who got hit, from the zombie vector.
This nested loop checks collision between every zombie and bullet one by one. It works good for a while but when I start killing more, at some point, it crashes because it wants to use a function of erased zombie.
for ( it = zombies.begin(); it != zombies.end(); ++it ) {
it->attack();
for (rambo.it = rambo.bullets.begin(); rambo.it != rambo.bullets.end(); ++rambo.it) {
if(checkBasicCollision(it,rambo.it) && it != zombies.end()){
zombies.erase(it);
}
}
}
I've added it--; after zombies.erase(it); works better now but it still crashes sometimes.
I think its happening like, for example there are 5 zombies and 20 bullets, zombie iterator is at second zombie, and second zombie starts the bullet loop to check if it got hit. Loop starts, lets say third bullet hit the zombie, but loop is still going, even if zombie is erased, it still continues the loop.
I've added break; after zombies.erase(it); now it hasn't got any problem. But the code looks so dirty. Is there another way to erase the current element easily
While the solution for manually erasing was presented, note that it is not the most idiomatic one. In idiomatic C++ you would make use of the std::remove_if algorithm in the erase-remove idiom like so:
// 1. A predicate that check whether a zombie was it by any bullet:
auto is_zombie_hit = [&rambo](Zombie const& zombie) {
auto is_bullet_hitting_zombie = [&zombie](Bullet const& bullet) {
return checkBasicCollision(zombie, bullet);
};
return std::any_of(
rambo.bullets.begin(),
rambo.bullets.end(),
is_bullet_hitting_zombie
);
};
// 2. Use the erase-remove idiom:
zombies.erase(
std::remove_if(zombies.begin(), zombies.end(), is_zombie_hit),
zombies.end()
);
Note: yes, you can use lambda in-place, however I prefer naming them to indicate their role.
Note: this uses C++11, however replacing lambda with predicates is trivial and an implementation of any_of is easy enough to produce, much like all_of and none_of.
To use erase you need to use the returned value and assign it back to the iterator so it is valid for the next iteration.
for ( it = zombies.begin(); it != zombies.end(); ) {
it->attack();
for (rambo.it = rambo.bullets.begin(); rambo.it != rambo.bullets.end(); ++rambo.it) {
if(checkBasicCollision(it,rambo.it) && it != zombies.end()){
it = zombies.erase(it); // erase will increment the iterator
}
else{
++it; // no erase, increment the iterator manually
}
}
}
From the documetion for vector::erase the return value is:
An iterator pointing to the new location of the element that followed the last element erased by the function call. This is the container end if the operation erased the last element in the sequence.
When erasing vector elements, iterators and indices become invalidated. Also your code is incorrect for the case 2 or more bullets hit the same zombie (if it is possible). Because with the second bullet the inner loop will try to erase the zombie that was already hit. Instead, you should do it like this:
for ( uint i = 0; i < zombies.size(); ++i)
{
for( auto it = rambo.bullets.begin(); it != rambo.bullets.end(); ++it)
{
if(checkBasicCollision(zombies[i], it)
{
zombies.erase( zombies.begin() + i );
--i;
break; // zombie is dead (well, technically it was already dead)
// so no further checks are needed (i.e. exit inner loop)
}
}
}
Perhaps something like this:
auto zombie_tail = std::remove_if(zombies.begin(), zombies.end(), [&](Zombie const & zombie) {
zombie.attack();
return std::any_of(rambo.bullets.begin(), rambo.bullets.end(), [&](Bullet const & bullet) {
return checkBasicCollision(zombie, bullet);
});
});
zombies.erase(zombie_tail, zombies.end());
Alternatively, if you wanted to stay away from c++ algorithms:
for (it = zombies.begin(); it != zombies.end(); ) {
it->attack();
// Attempt to find a bullet that hit.
for(rambo.it = rambo.bullets.begin(); rambo.it != rambo.bullets.end(); ++rambo.it)
if (checkBasicCollision(it, rambo.it))
break;
// Possibly remove the zombie, and advance the iterator
if (rambo.it != rambo.bullets.end())
it = zombies.erase(it);
else
++it;
}
direct, easy to read and grasp, but maybe not very fancy;
for ( auto& z : zombies )
z.attack();
for( auto& b : rambo.bullets )
{
auto itr = zombies.begin();
while( itr != zombies.end() )
{
if( checkBasicCollision(b,*itr) )
itr = zombies.erase(itr);
else
++itr;
}
}
checkBasicCollision now takes references, not iterators
I have a structure like this:
struct client
{
string name;
double money;
};
I also have 2 predicates:
bool less_10(const client& a)
{
return a.money < 10;
}
bool not_a(const client& a)
{
return a.name.at(0) != 'A';
}
In my main function I use this to filter out the result stored in vector client_list (everyone with money < 10 (choice 1) or everyone with name not start with A (else))
if (choice_filter == 1)
{
vector<client>::iterator it3;
it3 = find_if(client_list.begin(), client_list.end(), less_10);
while (it3 != client_list.end())
{
**client_list.erase(it3);
it3 = find_if(it3 + 1, client_list.end(), less_10);
}
client_list.erase(it3);**
}
else
{
vector<client>::iterator it4;
it4 = find_if(client_list.begin(), client_list.end(), not_a);
while (it4 != client_list.end())
{
**client_list.erase(it4);
it4 = find_if(it4 + 1, client_list.end(), not_a);
}
client_list.erase(it4);**
}
I notice that if I erase first, then find_if, i'll lost the last client. So i added 1 more line to erase, but the program crashes as iterator is now at the end, cant erase.
Is there any way to get around this? I want to keep using find_if with predicates as well as while loop like above as they are required.
As others have said, std::remove_if is the best solution. If
you're doing this for pedagogical reasons (which I suspect is
the case, given these particular predicates): you're on the
right track. The only issue is that client_list.erase
invalidates the iterator. But since it returns an iterator to
the element immediately after the element it erased, you can use
something like:
std::vector<Client>::iterator it
= std::find_if( client_list.begin(), client_list.end(), predicate );
while ( it != client_list.end() ) {
it = client_list.erase( it );
it = std::find_if( it, client_list.end(), predicate );
}
And you don't want to call erase after the loop. The iterator
designates the end, where there is no element to be erased.
The typical way to go is to use a temporary vector:
vector<client> tmp;
for (...)
{
if(predicate(it))
tmp.push_back(*it);
}
client_list.swap(tmp);
This is similar to what Chris suggested in a comment, although that solution would first move elements to the end of the vector and then truncate them from there. I'm not sure if that doesn't change the order on the way, just check the documentation. Depending on what you want, either could do the work though.
If you used a different container like list<> that did not invalidate all iterators in erase(), you could do this:
it = c.begin();
end = c.end();
while(it != end)
{
if(predicate(*it))
{
c.erase(it++);
}
else
{
++it;
}
}
Note that if you call erase(), you invalidate that iterator still, hence the iterator is first incremented and erase() is called with the former value using the postfix increment.
I also agree with chris, to using std::remove_if:
{
remove_if(client_list.begin(), client_list.end(), less_10);
}
But if you want to reinvent the wheel:
{
vector<client>::iterator it3 = client_list.begin();
while (true)
{
it3 = find_if(it3, client_list.end(), less_10);
if (it3 == client_list.end()) {
break;
}
it3 = client_list.erase(it3);
}
}
I have a list of Star structs. These structs are in a std::list
I am double looping this list and compairing there locations to detect a collision. When A collision is found I will delete Star with the lowest mass. But how can I delete the Star when I am in the double Loop, and keep the loop going to check for more collisions?
It's worth mentioning that the second loop is a reverse loop.
Here is some code
void UniverseManager::CheckCollisions()
{
std::list<Star>::iterator iStar1;
std::list<Star>::reverse_iterator iStar2;
bool totalbreak = false;
for (iStar1 = mStars.begin(); iStar1 != mStars.end(); iStar1++)
{
for (iStar2 = mStars.rbegin(); iStar2 != mStars.rend(); iStar2++)
{
if (*iStar1 == *iStar2)
break;
Star &star1 = *iStar1;
Star &star2 = *iStar2;
if (CalculateDistance(star1.mLocation, star2.mLocation) < 10)
{
// collision
// get heaviest star
if (star1.mMass > star2.mMass)
{
star1.mMass += star2.mMass;
// I need to delete the star2 and keep looping;
}
else
{
star2.mMass += star1.mMass;
// I need to delete the star1 and keep looping;
}
}
}
}
}
You need to utilize the return value of the erase method like so.
iStar1 = mStars.erase(iStar1);
erase = true;
if (iStar1 == mStars.end())
break; //or handle the end condition
//continue to bottom of loop
if (!erase)
iStar1++; //you will need to move the incrementation of the iterator out of the loop declaration, because you need to make it not increment when an element is erased.
if you don't increment the iterator if an item is erased and check if you deleted the last element then you should be fine.
Since modifying the list invalidates the iterators (so that you cannot increment them), you have to keep safe the iterators before the list is changed.
In the most of the implementation std::list is a dual-linked list, hence a iteration like
for(auto i=list.begin(), ii; i!=list.end(); i=ii)
{
ii = i; ++ii; //ii now is next-of-i
// do stuff with i
// call list.erasee(i).
// i is now invalid, but ii is already the "next of i"
}
The safest way, is to create a list containing all the "collided", then iterate on the "collided" calling list.remove(*iterator_on_collided)
(but inefficient, since has O2 complexity)
You want to use the result of erase() to get the next iterator and advance the loop differently:
If you erase using the outer iterator you clearly can abondon checking this Star against others and break out of the inner loop. Only if the inner loop was complete you'd want to advance the outer iterator because otherwise it would be advanced by the erase().
If you erase using the inner loop you already advanced the iteration, otherwise, i.e. if no star was erased, you need to advance.
Sample code would look somethimg like this:
for (auto oit(s.begin()), end(s.end()); oit != end; )
{
auto iit(s.begin());
while (iit != end)
{
if (need_to_delete_outer)
{
oit = s.erase(oit);
break;
}
else if (need_to_delete_inner)
{
iit = s.erase(iit);
}
else
{
++iit;
}
}
if (iit == end)
{
++oit;
}
}