STL Multimap Remove/Erase Values - c++

I have STL Multimap, I want to remove entries from the map which has specific value , I do not want to remove entire key, as that key may be mapping to other values which are required.
any help please.

If I understand correctly these values can appear under any key. If that is the case you'll have to iterate over your multimap and erase specific values.
typedef std::multimap<std::string, int> Multimap;
Multimap data;
for (Multimap::iterator iter = data.begin(); iter != data.end();)
{
// you have to do this because iterators are invalidated
Multimap::iterator erase_iter = iter++;
// removes all even values
if (erase_iter->second % 2 == 0)
data.erase(erase_iter);
}

Since C++11, std::multimap::erase returns an iterator following the last removed element.
So you can rewrite Nikola's answer slightly more cleanly without needing to introduce the local erase_iter variable:
typedef std::multimap<std::string, int> Multimap;
Multimap data;
for (Multimap::iterator iter = data.begin(); iter != data.end();)
{
// removes all even values
if (iter->second % 2 == 0)
iter = data.erase(iter);
else
++iter;
}
(See also answer to this question)

Related

Find and erase element from STL container while iterating

While iterating over a multimap I want to delete elements, but not only the element the iterator is pointing to.
for (vector<int> myVec : myVectors)
{
auto range = myMultiMap.equal_range(myVector);
for (auto it = range.first; it != range.second; ++it)
{
// secondPair is another element of this multimap
auto secondPair = getSecondPair(it, myMultiMap);
if (condition)
{
it = myMultiMap.erase(it);
auto finder = myMultiMap.find(secondPair);
// how can I delete secondPair?
}
}
}
Maybe it's the xy-problem here, so let me explain what I need this for: What I'm trying to do is to shorten a set of vector<int>. There are associated elements of type MyPair for each element. Those associated elements are stored in an unordered multimap.
typedef unordered_multimap < vector<int>, MyPair, SomeHash > MyMultiMap;
An element of the set<vector <int> > can be removed if all associated pairs in the multimap have been processed successfully. It won't be successful for most of them, so most of them are expected to remain in the set. My idea was to delete the elements from the multimap and if there are no associated elements in the multimap anymore, it means the element can be removed from the set.
Here again I have the problem to remove elements from a set while iterating. Again, not only the one the iterator is pointing to.
From cppreference on unordered_multimap::erase:
References and iterators to the erased elements are invalidated. Other iterators and references are not invalidated.
So I think that if you get an iterator to secondPair and secondPairIt != it then you can safely erase secondPairIt. You should also check that you are not invalidating the end of the range.
for (auto it = range.first; it != range.second;)
{
if (condition)
{
auto secondPairIt = getSecondPairIt(it, myMultiMap); // Assume this is not end
if (secondPairIt != it)
{
if (secondPairIt == range.second)
range.second = myMultiMap.erase(secondPairIt);
else
myMultiMap.erase(secondPairIt);
}
it = myMultiMap.erase(it);
}
else
{
++it;
}
}

Erase nested map elements

I have a map which looks like
typedef std::map<int, std::set<float>> innerMap;
typedef std::map<long, innerMap> outerMap;
I want to do following:
1. I want to erase inner map elements for a key of outer map.
2. Then I want to erase outer map key if it contains 0 inner map elements.
For first scenario, I have written code as:
outerMap mapA;
//assuming this map contain an element
//longx is key in outer element, intx is key of inner element
std::map<int, std::set<float>>::const_iterator innerIter = mapA[longx].begin();
while (innerIter != mapA[longx].end())
{
if (innerIter->first == intx)
{
if (innerIter->second.size() == 0)
{
mapA[longx].erase(innerIter++);
}
break;
}
++innerIter;
}
I have written this code in C++ but I wanna use C++11. Can we write this in a better way in C++11?
For second scenario, do I need to iterate again outer map and check its value elements or I can do it in existing code itself?
This code looks way too complicated to me. The following should do the same, no need to use fancy C++11 features:
outerMap mapA;
// Lookup iterator for element of outerMap.
outerMap::iterator const outerIter = mapA.find(longx);
// Lookup iterator for element of innerMap that should be checked.
innerMap::const_iterator const innerIter = outerIter->second.find(intx);
// Check if element of innerMap should be erased and erase it if yes.
if(innerIter != outerIter->second.end() && innerIter->second.size() == 0) {
outerIter->second.erase(innerIter);
}
// Erase element of outer map is inner map is now empty:
// This should do scenario 2
if(outerIter->second.size() == 0) {
mapA.erase(outerIter);
}
what you do currently (in C++11):
auto& inner = mapA[longx];
const auto it = inner.find(intx);
if (it != inner.end() && it->second.size() == 0) {
inner.erase(it);
}

Is there a .at() equivalent for a multimap?

Is there any way to get an iterator to a multimap, for specific keys? For example:
multimap<string,int> tmp;
tmp.insert(pair<string,int>("Yes", 1));
tmp.insert(pair<string,int>("Yes", 3));
tmp.insert(pair<string,int>("No", 5));
tmp.insert(pair<string,int>("Maybe", 1));
tmp.insert(pair<string,int>("Yes", 2));
multimap<string,int>::iterator it = tmp.at("Yes);
Then I could use it for the work I want to do. Is this possible in C++? Or do we have to just cycle through the multimap, element by element, and check for the key before doing the work?
You have find for a single key value pair (any matching the key), or equal_range to get all of the pairs that match a given key (this seems to be your best bet.)
multimap<Key, T> only sort elements by its Key, so we can only find all the elements whose key value equals "Yes", then check each element one by one.
typedef multimap<string,int>::iterator Iterator;
pair<Iterator, Iterator> iter_range = tmp.equal_range("Yes");
Iterator it;
for (it = iter_range.first; it != iter_range.second; ++it) {
if (it->second == 3) {
break;
}
}
if (it != tmp.end()) {
tmp.erase(it);
}
In fact it's better to use multiset<T> in this case:
multiset< pair<string, int> > temp;
temp.insert(make_pair("Yes", 1));
temp.insert(make_pair("Yes", 3));
multiset< pair<string, int> >::iterator iter = temp.find(make_pair("Yes", 1));
if (iter != temp.end()) {
temp.erase(iter); // it erase at most one element
}
temp.erase(make_pair("Yes", 3)); // it deletes all the elements that equal to make_pair("Yes", 3)

c++ map erase in range

I want to erase all the entries except the last two in a map. How could I do that? like the following?
std::map<int, obj>::iterator firstit = mymap.begin();
std::map<int, obj>::iterator lastit = mymap.end();
lastit--;
lastit--;
mymap.erase (firstit ,lastit);
You need to test that iterator is valid, if your mymap has less than 2 elements, your code invokes undefined behavior.
auto it = mymap.begin();
auto size = mymap.size();
if (size > 2)
{
std::advance(it, size - 2);
}
mymap.erase(mymap.begin(), it);
Looks fine to me assuming you do have at least two entries in your map.
--lastit; is sometimes claimed to be more efficient than lastit--; because the latter has to create a temporary iterator.

C++ multimap iterator invalidation

I'm trying to figure out how std::multimap iterators work, therefore I've created a simple example that shows the substance of my problem. If uncomment case 1, I expect iterator to point to the first element with the key 1, but in reality it prints all the values associated with key 0 (like nothing was erased) and sometimes it crashes, probably because iterator is invalid. However if uncomment case 2, all the values with key 1 are properly deleted.
Is there any way to know what is the next valid iterator for the multimap after erasure?
(for example std::vector.erase(...) returns one)
std::multimap<int, int> m;
for(int j=0; j<3; ++j) {
for(int i=0; i<5; ++i) {
m.insert(std::make_pair(j, i));
}
}
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end();) {
printf("%d %d\n", (*it).first, (*it).second);
++it;
if( (*it).second == 3 ) {
//m.erase(0); //case 1
m.erase(1); //case 2
}
}
The cause of the problem
When you call m.erase(0) in you example, it points at an element with the key 0 - so it is invalidated. m.erase(1) works, because when it is called the first time, it is not pointing to an element with the key 1, so it is not affected. In later iterations, no elements with the key 1 remain, so nothing is deleted, and no iterator is affected.
The Solution
multimap does not have an erase-method that returns the next valid iterator. One alternative is to call it = m.upper_bound(deleted_key); after the deletion. This is logarithmic, though, which might be too slow for your scenario (erase(x) and upper_bound would be two logarithmic operations).
Assuming you want to erase the key your iterator is currently pointing to, you could do something like this (otherwise, erase is fine, of course; not tested):
std::multimap<int, int>::iterator interval_start = m.begin();
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end(); ++it) {
if(interval_start->first < it->first) // new interval starts here
interval_start == it;
if( (*it).second == 3 ) {
std::multimap<int, int>::iterator interval_end = it;
while((interval_end != m.end()) && (interval_end->first == it->first)) {
++interval_end; // search for end of interval - O(n)
}
m.erase(interval_start, interval_end); // erase interval - amortized O(1)
it = interval_end; // set it to first iterator that was not erased
interval_start = interval_end; // remember start of new interval
}
}
This uses one linear operation, all the rest are constant time. If your map is very large, and you only have few items with equal keys, this will likely be faster. However, if you have many items with equal keys, the search for the end of the interval, is probably better done using upper_bound (O(log n) instead of O(n) when searching the end of the interval).
when you erase the iterator becomes invalid. instead remember the next element then erase:
std::map<int,int>::iterator next = m + 1;
m.erase
m = next;
First answer
std::multimap<int, int> m;
// ^^^^^^^^
std::map<int, int>::iterator it=m.begin();
// ^^^
Hum....
Second answer, re: edited question
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end();) {
.... stuff ....
m.erase(1); // container mutation
.... stuff ....
}
Be extremely careful when you are mutating a container (any container) when you are iterating on it, as you might invalidate an iterator you depend on.
The so-called "node-based containers" (list, set, map...) are the most robust container WRT iterator invalidation: they only invalidate iterators to deleted elements (there is no way for these iterators not be invalidated).
In this case you should check that the element you are about to delete isn't actually *it.
I am not quite sure what you are trying really to do with your loop.
From looking at your code, I think that your ++it is causing the problem. You are assigning it to a place that might have been deleted. move it to the end, after the if statement and test. like so:
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end();) {
printf("%d %d\n", (*it).first, (*it).second);
if( (*it).second == 3 ) {
//m.erase(0); //case 1
m.erase(1); //case 2
}
++it;
}
(Edited)
for(std::multimap<int, int>::iterator it=m.begin(); it!=m.end();) {
printf("%d %d\n", (*it).first, (*it).second);
++it;
if( (*it).second == 3 ) {
//m.erase(0); //case 1
m.erase(1); //case 2
}
}
In addition to invalidation of it iterator due to m.erase that may occur depending on the contents of multimap (already covered in another answer) there is always the problem that you dereference m.end() iterator on the last iteration of your for loop when you do if( (*it).second == 3 ) each time you run your program.
I suggest to run and debug with debug builds. I'm almost sure that every sane standard library implementation should contain assert to detect end() dereferencing.
Some guys above already have answered that you are playing with a fire.
Also, I think you are forgetting that multimap is ordered map, so you are iterating from the smallest keys to the largest ones. Therefore in the first case you remove keys after printing some of them, but in the second case you are remove just before going to them.