remove_if equivalent for std::map
Almost.
for(; iter != endIter; ) {
if (Some Condition) {
iter = aMap.erase(iter);
} else {
++iter;
}
}
What you had originally would increment the iterator twice if you did erase an element from it; you could potentially skip over elements that needed to be erased.
This is a common algorithm I've seen used and documented in many places.
[EDIT] You are correct that iterators are invalidated after an erase, but only iterators referencing the element that is erased, other iterators are still valid. Hence using iter++
in the erase()
call.
using remove_if for a map container
remove_if
works by scanning the elements and once an element is to be removed, it remembers the "gap" that will leave (keeping an iterator pointing thereto) while advancing another iterator to find the next element to retain... it then starts copying or moving elements from the latter position to the former until it reaches end()
.
That doesn't work for map
, because you can't overwrite the pair<key,value>
elements wholesale: the key values aren't allowed to be modified or the sorted-order invariant the implementation needs could be invalidated.
So, you'll need to abandon remove_if
. You could use a normal loop, being careful to save the iterator-to-next-element rather than attempting to advance from a just-erased iterator. Lots of other questions about how to erase elements from a map while iterating, e.g. here....
map, lambda, remove_if
The problem is that std::map<K,V>::value_type
is std::pair<const K, V>
, aka .first
is const
and not assignable. Lambdas have nothing to do with the problem here.
std::remove_if
"removes" items by moving the elements of the container around, so that everything that does not fit the predicate is at the front, before the returned iterator. Everything after that iterator is unspecified. It does that with simple assignment, and since you can't assign to a const
variable, you get that error.†
The name remove
can be a bit misleading and in this case, you really want erase_if
, but alas, that doesn't exist. You'll have to make do with iterating over all items and erasing them by hand with map.erase(iterator)
:
for(auto it = map.begin(), ite = map.end(); it != ite;)
{
if(it->second._id == remove_id)
it = map.erase(it);
else
++it;
}
This is safe because you can erase individual nodes in the tree without the other iterators getting invalidated. Note that I did not increment the iterator in the for loop header itself, since that would skip an element in the case where you erase a node.
† By now, you should have noticed that this would wreak havoc in the std::map
's ordering, which is the reason why the key is const
- so you can't influence the ordering in any way after an item has been inserted.
Erase specific elements in std::map
Write it like this for map, since remove_if
won't work for map
iterators (it merely puts offending elements at the end, and map
iterators don't allow for this):
template <typename Map, typename F>
void map_erase_if(Map& m, F pred)
{
typename Map::iterator i = m.begin();
while ((i = std::find_if(i, m.end(), pred)) != m.end())
m.erase(i++);
}
or if you like one-liners:
template <typename Map, typename F>
void map_erase_if(Map& m, F pred)
{
for (typename Map::iterator i = m.begin();
(i = std::find_if(i, m.end(), pred)) != m.end();
m.erase(i++));
}
Remove elements from map with keys not containing in another vector
My first instinct is to erase chunks of non-real objects in bulk:
// Assuming real_objets is sorted (otherwise sort it)
auto first = some_container.begin();
for(int i : real_objects) {
// end of the chunk to erase is the place where i could be
// inserted without breaking the order
auto last = some_container.lower_bound(i);
some_container.erase(first, last);
// if i is a key in the container, skip that element
if(last != some_container.end() && last->first == i) {
++last;
}
first = last;
}
// and in the end, kill stuff that comes after real_objects.back()
some_container.erase(first, some_container.end());
This has runtime complexity O(n * log(m)), where n is real_objects.size()
and m is some_container.size()
, meaning it performs best if some_container
is much larger than real_objects
. Otherwise, since it is possible to iterate through a std::map
in linear time, you could walk through both in lock-step and remove discrepancies in order:
// again, sort real_objects.
if(!some_container.empty()) { // else there's nothing to do
auto ir = real_objects.begin();
auto ire = std::lower_bound(real_objects.begin(),
real_objects.end (),
some_container.rbegin()->first);
auto is = some_container.begin();
for(;;) {
// find the next place where the real_objects and the keys of some_container don't match
auto p = std::mismatch(ir, ire, is, [](int i, std::pair<int, int> const &p) { return i == p.first; });
if(p.first == real_objects .end() ||
p.second == some_container.end())
{
// upon reaching the end, remove all that comes after the
// end of real_objects (if anything)
some_container.erase(p.second, some_container.end());
break;
} else if(*p.first < p.second->first) {
// if there's something in real_objects that's not in
// some_container, skip it
++p.first;
} else {
// otherwise there's something in some_container that's
// not in real_objects, so delete it.
p.second = some_container.erase(p.second);
}
ir = p.first;
is = p.second;
}
}
This has runtime complexity O(max(n, m)), so it should perform well if some_container
and real_objects
almost match.
remove elements from `map` that are not in `set`
I'd start with this simple approach:
for (auto it = myMap.begin(); it != myMap.end(); )
{
if (mySet.find(it->first) == mySet.end()) { myMap.erase(it++); }
else { ++it; }
}
If you want something more efficient, you could iterate both containers in lockstep and do key-wise comparisons to take advantage of the compatible element order. On the other hand, the present algorithm works even on unordered containers, and given that your keys are strings, unordered containers may have a better performance anyway.
Finding and erasing a value from a std::vector holding std::map elements
You can also go a little bit functional: Playground
#include <algorithm>
#include <functional>
// removes maps from x1, that are equal to none of x2 maps
auto remove_start = std::remove_if(x1.begin(), x1.end(), [&](const auto& x1_map){
return std::none_of(x2.begin(), x2.end(),
std::bind(std::equal_to(), x1_map, std::placeholders::_1));
});
x1.erase(remove_start, x1.end());
EDIT: To check keys only, change std::equal_to to a custom lambda
auto keys_equal = [](auto& m1, auto& m2){
return m1.size() == m2.size()
&& std::equal(m1.begin(), m1.end(), m2.begin(),
[](auto& kv1, auto& kv2){ return kv1.first == kv2.first; });
};
// removes maps from x1, that are equal to none of x2 maps
auto remove_start =
std::remove_if(x1.begin(), x1.end(), [&](const auto& x1_map){
return std::none_of(x2.begin(), x2.end(),
std::bind(keys_equal, x1_map, std::placeholders::_1));
});
x1.erase(remove_start, x1.end());
Erase/Remove contents from the map (or any other STL container) while iterating it
bool IsOdd( int i )
{
return (i&1)!=0;
}
int a[] = {1,2,3,4,5};
vector<int> v( a, a + 5 );
v.erase( remove_if( v.begin(), v.end(), bind1st( equal_to<int>(), 4 ) ), v.end() );
// v contains {1,2,3,5}
v.erase( remove_if( v.begin(), v.end(), IsOdd ), v.end() );
// v contains {2}
Related Topics
Proper Stack and Heap Usage in C++
How to Make Cmake Output into a 'Bin' Dir
Undefined Behavior and Sequence Points Reloaded
Purpose of Returning by Const Value
Incrementing in C++ - When to Use X++ or ++X
Where Does Visual Studio Look For C++ Header Files
Boolean Expression (Grammar) Parser in C++
Have You Used Any of the C++ Interpreters (Not Compilers)
Is Ncurses Available For Windows
Why Do C++11-Deleted Functions Participate in Overload Resolution
How to Construct a Std::String With Embedded Values, I.E. "String Interpolation"
Convert String to Variable Name or Variable Type
Why Can't Simple Initialize (With Braces) 2D Std::Array
Setting the Internal Buffer Used by a Standard Stream (Pubsetbuf)