C++ Remove_If on a Vector of Objects

C++ remove_if on a vector of objects

Should I create a member variable in the object which allows me to say
that I want to delete the myobj and then create a predicate which
checks to see if the member variable was set?

Haven't you already done that? Isn't that what m_bMarkedDelete is for? You would write the predicate like this:

bool IsMarkedToDelete(const myobj & o)
{
return o.m_bMarkedDelete;
}

Then:

myList.erase(
std::remove_if(myList.begin(), myList.end(), IsMarkedToDelete),
myList.end());

Or, using lambdas:

myList.erase(
std::remove_if(myList.begin(), myList.end(),
[](const myobj & o) { return o.m_bMarkedDelete; }),
myList.end());

If your class doesn't actually have that member, and you're asking us if it should, then I would say no. What criteria did you use to decide to mark it for deletion? Use that same criteria in your predicate, for example:

bool IndexGreaterThanTen(const myobj & o)
{
return o.index > 10;
}

note -- The functions I've written are of course invalid since all your members are private. So you'll need some way to access them.

Using a std::remove_if for vector of non-pointer types

The predicate passed to std::remove_if is supposed to be a unary predicate with signature like bool pred(const Type &a);. That means it will be invoked with the element as the argument. But wasMessageSend is a member function so, besides the argument, it also needs an instance to be invoked on. So it can't be used with remove_if directly.

You can use lambda (since C++11) with it like:

_messages.erase(std::remove_if(
_messages.begin(),
_messages.end(),
[this](const QueuedMessage& q) {
return wasMessageSend(q);
}
));

remove_if comparing two elements in the same vector

So, I don't believe that your current code works for all cases. And therefore, I think it needs a larger fix.

For example, let's assume that you have 3 objects in your vector, and all 3 of them interact. Your code will grab the first two, and then remove them. When we get to the third, there won't be anything for it to interact with, so it will remain. But that's not correct behavior.

I've written some code that works as follows:

  • We take the vector, and hold a start and end position. These iterators represent the valid remaining area to work over.

  • Three areas within our vector:

    • Elements between [vec.begin(), start) are objects that we know we will keep, and have compared them already against everything in the vector. So we don't need to touch them any more.
    • Elements between [start, end), are elements that we still need to compare things against to see if they have any interactions.
    • Elements between [end, vec.end()) are elements that we know are interacting with other things. However, there may still be more elements that they interact with that we haven't found yet.
  • Our end condition is when start == end. This works because at this point, there are no more elements to compare for interactions.

Code Follows:

#include <algorithm>
#include <iostream>
#include <vector>

struct myObj {
myObj(int val): val(val) {}

bool interactsWith(struct myObj *other) {
return this->val == other->val;
}

const int val;
};

int main() {
std::vector<myObj *> vec;
vec.push_back(new myObj(3));
vec.push_back(new myObj(2));
vec.push_back(new myObj(1));
vec.push_back(new myObj(2));

// Print the vector
for (auto x : vec)
std::cout << x->val << " ";
std::cout << "\n";

auto start = vec.begin();
auto end = vec.end();

while (start != end) {
// Test if start interacts with anything in the vector
std::vector<myObj *>::iterator match;
for (match = start + 1; match != vec.end(); ++match)
if ((*start)->interactsWith(*match))
break;

// We did not find a match
if (match == vec.end()) {
start++;
continue;
}

// If the match isn't already in the removal area, move it there.
if (match < end)
std::iter_swap(match, --end);

// The start is always before the removal area, so move it there.
std::iter_swap(start, --end);
}

// Print the vector
for (auto x : vec)
std::cout << x->val << " ";
std::cout << "\n";

// Delete the memory backing these elements
// We should probably just be using std::vector<std::unique_ptr>>...
std::for_each(end, vec.end(), [](myObj * & obj) {
delete obj;
obj = NULL;
});

// Remove the elements from the vector
vec.erase(end, vec.end());

// Print the vector
for (auto x : vec)
std::cout << x->val << " ";
std::cout << "\n";
}

This will output:

[4:04pm][wlynch@apple /tmp] ./foo
3 2 1 2 // The original vector
3 1 2 2 // The elements that intersect have been moved to the back of the vector
3 1 // We've erased those elements from the vector.

And a note:

This ends up being very similar to NicholasM's answer with a few optimizations that are not presant in his.

  • Once we find an element does not intersect with anything, we can ignore it.
  • When we find an intersecting match, we can move both elements to the end of the list.

Both of these optimizations are allowed because intersectsWith() is associative.

c++ remove custom object from vector : std::remove_if': no matching overloaded function found

The syntax for forming a pointer to member function is &ClassName::FunctionName. So you need &GameWorld::beeToClose for a pointer to the beeToClose member function. In your case, you should use a lambda from which you call that function

auto q = std::remove_if(bees.begin(), bees.end(),
[&](shared_ptr<MovingEntity> const& bee){ return beeToClose(bee); });

Also, you're using the wrong vector::erase overload, you want the one that erases a range of elements, not the one that erases a single element.

bees.erase(q, bees.end());

How do delete items in a vector if using remove_if and items are pointers to objects?

You could just delete the item in your IsMarkedToDelete function when it returns true.

Using erase-remove_if idiom

The correct code is:

stopPoints.erase(std::remove_if(stopPoints.begin(),
stopPoints.end(),
[&](const stopPointPair stopPoint)-> bool
{ return stopPoint.first == 4; }),
stopPoints.end());

You need to remove the range starting from the iterator returned from std::remove_if to the end of the vector, not only a single element.

"Why?"

  • std::remove_if swaps elements inside the vector in order to put all elements that do not match the predicate towards the beginning of the container. This means that if the predicate (body of the lambda function) returns true, then that element will be placed at the end of the vector.

  • remove_if then **returns an iterator which points to the first element which matches the predicate **. In other words, an iterator to the first element to be removed.

  • std::vector::erase erases the range starting from the returned iterator to the end of the vector, such that all elements that match the predicate are removed.


More information: Erase-remove idiom (Wikipedia).

Remove object from vector if private variable matches

The correct way of doing this is to use the combination of std::remove / std::remove_if and erase.

You can find the documentation in the CPP Reference here and here. In the std::remove example code, you can see also the combination of remove and erase. You can also find tons of similar examples here on SO.

std::remove_if will use a simple lambda that checks, if the item name matches the search name.

Please see:

#include <iostream>
#include <string>
#include <vector>

class Item {
private:
std::string name;
int power;
public:
Item(std::string n, int p) {
name = n; power = p;
}

std::string getName() {
return name;
}
};
class Inventory {
private:
std::vector<Item> items;
public:
void addItem(Item item) {
items.push_back(item);
}

void removeItem(std::string n) {
items.erase(std::remove_if(items.begin(), items.end(), [n](Item& i) { return i.getName() == n; }), items.end());
}
};

int main() {

Inventory inventory{};

// Add 20 item as a demo
for (int i{}; i < 20; ++i) inventory.addItem(Item(std::to_string(i), i));

// Remove Item "3"
inventory.removeItem("3");

return 0;
}

remove_if without the need of move and copy semantics

If object is non-movable/copyable how do you expect to rearrange objects in the vector? Vector is a contiguous container - so you cannot erase an element in the middle without shifting the ending.

To resolve the issue, either make the object movable/copyable, wrap it in unique_ptr (thus making it movable), or use one of map/set/unordered_map containers as those don't relocate objects unlike std::vector



Related Topics



Leave a reply



Submit