remove elements from `map` that are not in `set` - c++

std::map<std::string, Obj> myMap;
std::set<std::string> mySet;
I want to remove those pairs from myMap which keys are not in mySet.
How do I do it? I found std::remove_if algorithm, but it seems to not be applicable here.

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.

Related

Why we need rbegin and rend?

Since we now have advance() and the prev() to move iterator to go front or go back, and we already have begin() and end().
I wonder is there any situation we better/have to move reverse iterator back and front?
Algorithms often take two iterators that specify a range of elements. For example std::for_each:
std::vector<int> x;
std::for_each(x.begin(),x.end(),foo);
If you want to make for_each iterate in reverse order (note: for_each does iterate in order) then neither advance nor prev are of any help, but you can use reverse iterators:
std::for_each(x.rbegin(),x.rend(),foo);
Because using begin() and end() to iterate in reverse looks horrible:
std::vector<int> v {1, 2, 3};
if(!v.empty()) { //need to make sure of that before we decrement
for(auto it = std::prev(v.end()); ; --it) {
//do something with it
if(it == v.begin()) {
break;
}
}
}
Compare it with reverse iterator version:
std::vector<int> v {1, 2, 3};
for(auto it = v.rbegin(); it != v.rend(); it++) {
//do something with it
}
When you have a function template that takes iterators, and want it to operate on the data in reverse.
E.g.
std::string s = "Hello";
std::string r(s.rbegin(), s.rend());
std::cout << r;
When you use algorithms like std::for_each(), std::accumulate(), std::find_if()... they systematically progress with ++.
If you want this progression to physically occur backwards, then the reverse
iterators are useful.
I guess it is good practise because it seems odd if you start from end and finish in begin. You can easily say last but one by using rbegin.
vector::reverse_iterator itr1;
for (itr1 = vec.rbegin(); itr1 < vec.rend(); itr1++) {
if (*itr1 == num) {
vec.erase((itr1 + 1).base());
}
}
You can use as a function which deletes that Which num want to erase in vector
The need for rbegin()/rend() is because begin() is not the same as rend(), and end() is not rbegin(), see this image from cppreference
This way, you can use any algorithm going forward from beginning to end or backwards from the last to the first element.
There are examples with for each. However, more general, it allows you to reuse any algorithm or operators that works with iterators with advancing, to do the same thing but in a reverse order.

Erasing many vector elements while going through it with 'auto'

Let's say that I have vector of pairs, where each pair corresponds to indexes (row and column) of certain matrix I am working on
using namespace std;
vector<pair<int, int>> vec;
I wanted to, using auto, go through the whole vector and delete at once all the pairs that fulfill certain conditions, for example something like
for (auto& x : vec) {
if (x.first == x.second) {
vec.erase(x);
}
}
but it doesn't work, as I suppose vec.erase() should have an iterator as an argument and x is actually a pair that is an element of vector vec, not iterator. I tried to modify it in few ways, but I am not sure how going through container elements with auto exactly works and how can I fix this.
Can I easily modify the code above to make it work and to erase multiple elements of vector, while going through it with auto? Or I should modify my approach?
For now it's just a vector of pairs, but it will be much worse later on, so I would like to use auto for simplicity.
vector::erase() invalidates any outstanding iterators, including the one your range based for loop is using. Use std::remove_if():
vec.erase(
std::remove_if(
vec.begin(),
vec.end(),
[](const pair<int,int> &xx) { return xx.first == xx.second; }
), vec.end()
);
std::remove_if() swaps the elements to the end of the vector and then you can safely erase them.
I would prefer something like this:
pair<int, int> pair = nullptr;
auto iter = vec.begin();
while(iter != vec.end()){
pair = (*iter);
if(pair.first == pair.second){
iter = this->vec.erase(iter);
}else{
++iter;
}
}

traverse the std::map with less than comparison between iterators

When I'd like to traverse a map in C++, we may use the following technique:
for (auto i = m.begin(); i != m.end(); i++)
{ ... ... }
Why cannot we use the following instead:
for (auto i = m.begin(); i < m.end(); i++)
{ ... ... }
My guess is because the elements in an associative container are not stored in sequential order like sequential containers, is that right?
The comparison operator < requires random access iterators.
map only provides Bidirectional iterators. The reason is that you cannot say with just such an iterator if another iterator is before or after in constant time (yes, they are not one after the other in memory).
As != is valid for all types of iterators, use it instead of the < version. It's portable if you change the container type.

Efficiently iterate multiple maps with the same keys

If I have two maps which are guaranteed to have exactly the same set of keys, how can I efficiently iterate through both maps?
For example, say I have the following maps:
std::map<std::string, int> iMap;
std::map<std::string, std::vector<int> > vMap;
At some point they both end up with exactly the same set of keys. I now need to update all values of vMap based on the corresponding iMap value. The first thing that comes to mind is something this:
typedef map<string, int> map_t;
BOOST_FOREACH(map_t::value_type &p, iMap) {
vMap[p.first].push_back(p.second);
}
However, it seems rather wasteful that we have to lookup each value of vMap[n] considering we're effectively going through the keys in order. Is there any way we can take advantage of this?
If you're absolutely sure that the keys are identical, you can iterate over both maps in lockstep:
auto it1 = iMap.begin();
auto it2 = vMap.begin();
while (it1 != iMap.end())
{
it2->second.push_back(*it1);
++it1;
++it2;
}

remove_if equivalent for std::map

I was trying to erase a range of elements from map based on particular condition. How do I do it using STL algorithms?
Initially I thought of using remove_if but it is not possible as remove_if does not work for associative container.
Is there any "remove_if" equivalent algorithm which works for map ?
As a simple option, I thought of looping through the map and erase. But is looping through the map and erasing a safe option?(as iterators get invalid after erase)
I used following example:
bool predicate(const std::pair<int,std::string>& x)
{
return x.first > 2;
}
int main(void)
{
std::map<int, std::string> aMap;
aMap[2] = "two";
aMap[3] = "three";
aMap[4] = "four";
aMap[5] = "five";
aMap[6] = "six";
// does not work, an error
// std::remove_if(aMap.begin(), aMap.end(), predicate);
std::map<int, std::string>::iterator iter = aMap.begin();
std::map<int, std::string>::iterator endIter = aMap.end();
for(; iter != endIter; ++iter)
{
if(Some Condition)
{
// is it safe ?
aMap.erase(iter++);
}
}
return 0;
}
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.
erase_if for std::map (and other containers)
I use the following template for this very thing.
namespace stuff {
template< typename ContainerT, typename PredicateT >
void erase_if( ContainerT& items, const PredicateT& predicate ) {
for( auto it = items.begin(); it != items.end(); ) {
if( predicate(*it) ) it = items.erase(it);
else ++it;
}
}
}
This won't return anything, but it will remove the items from the std::map.
Usage example:
// 'container' could be a std::map
// 'item_type' is what you might store in your container
using stuff::erase_if;
erase_if(container, []( item_type& item ) {
return /* insert appropriate test */;
});
Second example (allows you to pass in a test value):
// 'test_value' is value that you might inject into your predicate.
// 'property' is just used to provide a stand-in test
using stuff::erase_if;
int test_value = 4; // or use whatever appropriate type and value
erase_if(container, [&test_value]( item_type& item ) {
return item.property < test_value; // or whatever appropriate test
});
Now, std::experimental::erase_if is available in header <experimental/map>.
See: http://en.cppreference.com/w/cpp/experimental/map/erase_if
Here is some elegant solution.
for (auto it = map.begin(); it != map.end();)
{
(SomeCondition) ? map.erase(it++) : (++it);
}
For those on C++20 there are built-in std::erase_if functions for map and unordered_map:
std::unordered_map<int, char> data {{1, 'a'},{2, 'b'},{3, 'c'},{4, 'd'},
{5, 'e'},{4, 'f'},{5, 'g'},{5, 'g'}};
const auto count = std::erase_if(data, [](const auto& item) {
auto const& [key, value] = item;
return (key & 1) == 1;
});
I got this documentation from the excellent SGI STL reference:
Map has the important property that
inserting a new element into a map
does not invalidate iterators that
point to existing elements. Erasing an
element from a map also does not
invalidate any iterators, except, of
course, for iterators that actually
point to the element that is being
erased.
So, the iterator you have which is pointing at the element to be erased will of course be invalidated. Do something like this:
if (some condition)
{
iterator here=iter++;
aMap.erase(here)
}
The original code has only one issue:
for(; iter != endIter; ++iter)
{
if(Some Condition)
{
// is it safe ?
aMap.erase(iter++);
}
}
Here the iter is incremented once in the for loop and another time in erase, which will probably end up in some infinite loop.
From the bottom notes of:
http://www.sgi.com/tech/stl/PairAssociativeContainer.html
a Pair Associative Container cannot provide mutable iterators (as defined in the Trivial Iterator requirements), because the value type of a mutable iterator must be Assignable, and pair is not Assignable. However, a Pair Associative Container can provide iterators that are not completely constant: iterators such that the expression (*i).second = d is valid.
First
Map has the important property that inserting a new element into a map does not invalidate iterators that point to existing elements. Erasing an element from a map also does not invalidate any iterators, except, of course, for iterators that actually point to the element that is being erased.
Second, the following code is good
for(; iter != endIter; )
{
if(Some Condition)
{
aMap.erase(iter++);
}
else
{
++iter;
}
}
When calling a function, the parameters are evaluated before the call to that function.
So when iter++ is evaluated before the call to erase, the ++ operator of the iterator will return the current item and will point to the next item after the call.
IMHO there is no remove_if() equivalent.
You can't reorder a map.
So remove_if() can not put your pairs of interest at the end on which you can call erase().
Based on Iron Savior's answer For those that would like to provide a range more along the lines of std functional taking iterators.
template< typename ContainerT, class FwdIt, class Pr >
void erase_if(ContainerT& items, FwdIt it, FwdIt Last, Pr Pred) {
for (; it != Last; ) {
if (Pred(*it)) it = items.erase(it);
else ++it;
}
}
Curious if there is some way to lose the ContainerT items and get that from the iterator.
Steve Folly's answer I feel the more efficient.
Here is another easy-but-less efficient solution:
The solution uses remove_copy_if to copy the values we want into a new container, then swaps the contents of the original container with those of the new one:
std::map<int, std::string> aMap;
...
//Temporary map to hold the unremoved elements
std::map<int, std::string> aTempMap;
//copy unremoved values from aMap to aTempMap
std::remove_copy_if(aMap.begin(), aMap.end(),
inserter(aTempMap, aTempMap.end()),
predicate);
//Swap the contents of aMap and aTempMap
aMap.swap(aTempMap);
If you want to erase all elements with key greater than 2, then the best way is
map.erase(map.upper_bound(2), map.end());
Works only for ranges though, not for any predicate.
I use like this
std::map<int, std::string> users;
for(auto it = users.begin(); it <= users.end()) {
if(<condition>){
it = users.erase(it);
} else {
++it;
}
}