Iterate over std::multimap and delete certain entries - c++

I want to iterate over all items in a std::multimap (all values of all keys), and delete all entries that satisfy some condition:
#include <map>
typedef int KEY_TYPE;
typedef int VAL_TYPE;
bool shouldRemove(const KEY_TYPE&, const VAL_TYPE&);
void removeFromMap(std::multimap<KEY_TYPE,VAL_TYPE>& map){
for (auto it = map.begin(); it != map.end(); it++){
if (shouldRemove(it->first,it->second))
map.erase(it);
}
}
The iteration works unless the first item gets deleted, and the following error is thrown then:
map/set iterator not incrementable
How can the removeFromMap function be rewritten in order to work properly? The code should work for all kinds of key- and value types of the map.
I am using C++ 11 and Visual Studio 2013.

You need to increment your iterator before you do the erase. When you do map.erase(it); the iterator it becomes invalid. However, other iterators in the map will still be valid. Therefore you can fix this by doing a post-increment on the iterator...
auto it = map.begin();
const auto end = map.end();
while (it != end)
{
if (shouldRemove(it->first,it->second))
{
map.erase(it++);
// ^^ Note the increment here.
}
else
{
++it;
}
}
The post-increment applied to it inside the map.erase() parameters will ensure that it remains valid after the item is erased by incrementing the iterator to point to the next item in the map just before erasing.
map.erase(it++);
... is functionally equivalent to...
auto toEraseIterator = it; // Remember the iterator to the item we want to erase.
++it; // Move to the next item in the map.
map.erase(toEraseIterator); // Erase the item.
As #imbtfab points out in the comments, you can also use it = map.erase(it) to do the same thing in C++11 without the need for post-incrementing.
Note also that the for loop has now been changed to a while loop since we're controlling the iterator manually.
Additionally, if you're looking to make your removeFromMap function as generic as possible, you should consider using template parameters and pass your iterators in directly, rather than passing in a reference to the multi-map. This will allow you to use any map-style container type, rather than forcing a multimap to be pased in.
e.g.
template <typename Iterator>
void removeFromMap(Iterator it, const Iterator &end){
...
}
This is how the standard C++ <algorithm> functions do it also (e.g. std::sort(...)).

Related

Why boost filter_iterator has weird make_filter_iterator function?

after some pain I managed to hack together this minimal example of boost filter_iterator
using namespace std;
std::function<bool(uint32_t)> stlfunc= [](uint32_t n){return n%3==0;};
int main()
{
vector<uint32_t> numbers{11,22,33,44,55,66,77,3,6,9};
auto start = boost::make_filter_iterator(stlfunc, numbers.begin(), numbers.end());
auto end = boost::make_filter_iterator(stlfunc, numbers.end() , numbers.end());
auto elem = std::max_element(start,end);
cout << *elem;
}
It works nice, but I wonder why the make_filter_iterator takes numbers.end()?
I might be wrong to use it that way, I guestimated it from the C array example:
http://www.boost.org/doc/libs/1_53_0/libs/iterator/example/filter_iterator_example.cpp
That is explained in the docs:
When skipping over elements, it is necessary for the filter adaptor to
know when to stop so as to avoid going past the end of the underlying
range. A filter iterator is therefore constructed with pair of
iterators indicating the range of elements in the unfiltered sequence
to be traversed.
From the source below you can see the always check if they have reached the end in satisfy_predicate:
void increment()
{
++(this->base_reference());
satisfy_predicate();
}
void satisfy_predicate()
{
while (this->base() != this->m_end && !this->m_predicate(*this->base()))
++(this->base_reference());
}
Also, as pointed out by Alex Chamberlain, the
constructors make it optional when passing the end iterator, for example: filter_iterator(Iterator x, Iterator end = Iterator()); (provided it is default constructible). So, you could omit numbers.end() from your code when constructing the end iterator.
If you look at the declaration of your make_filter_iterator template, you see it looks like this:
template <class Predicate, class Iterator>
filter_iterator<Predicate,Iterator>
make_filter_iterator(Predicate f, Iterator x, Iterator end = Iterator());
Specifically you see that the last parameter is a default parameter, and it's set to Iterator() which would mean it's default contructed and for some types of iterator it behaves like actual end() iterator, which points one past the end of any array, ie it points to garbage.
Most container types do require an actual end() iterator to be passed.

Using std::deque::iterator (in C++ STL) for searching and deleting certain elements

I have encountered a problem invoking the following code:
#include<deque>
using namespace std;
deque<int> deq = {0,1,2,3,4,5,6,7,8};
for(auto it = deq.begin(); it != deq.end(); it++){
if(*it%2 == 0)
deq.erase(it);
}
which resulted in a segmentation fault. After looking into the problem I found that the problem resides in the way the STL manages iterators for deques: if the element being erased is closer to the end of the deque, the iterator used to point to the erased element will now point to the NEXT element, but not the previous element as vector::iterator does. I understand that modifying the loop condition from it != deq.end() to it < deq.end() could possibly solve the problem, but I just wonder if there is a way to traverse & erase certain element in a deque in the "standard form" so that the code can be compatible to other container types as well.
http://en.cppreference.com/w/cpp/container/deque/erase
All iterators and references are invalidated [...]
Return value : iterator following the last removed element.
This is a common pattern when removing elements from an STL container inside a loop:
for (auto i = c.begin(); i != c.end() ; /*NOTE: no incrementation of the iterator here*/) {
if (condition)
i = c.erase(i); // erase returns the next iterator
else
++i; // otherwise increment it by yourself
}
Or as chris mentioned you could just use std::remove_if.
To use the erase-remove idiom, you'd do something like:
deq.erase(std::remove_if(deq.begin(),
deq.end(),
[](int i) { return i%2 == 0; }),
deq.end());
Be sure to #include <algorithm> to make std::remove_if available.

vector iterators incompatible while erase from vector

I have a map which elements are vectors.I have to delete from these vectors all elements which are equal to special number num
std::map<size_t,std::vector<size_t> > myMap;
for (std::map<size_t,std::vector<size_t> >::iterator itMap = myMap.begin();itMap != myMap.end();++itMap )
{
for (std::vector<size_t>::iterator itVec = itMap->second.begin();itVec != itMap->second.end();)
{
auto itNextVec = itVec;
++itNextVec;
if (*itVec == num)
{
itMap->second.erase(itVec );
}
itVec = itNextVec;
}
}
The code causes run-time exepssion .In VS - vector iterators incompatible.
Can someone point what is the cause for that?
Thanks
std::vector::erase returns an iterator to the next position of the list, and so when you do an erase you should make your iterator equal to the returned value.
The only thing that you have to consider is that the returned iterator could be the end so you should check for that.
What I personally like to do is is after doing in an erase and I get the next iterator position, I go back to the previous position of the returned iterator and than call a continue on the for loop
Example:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> myInt;
myInt.push_back(1);myInt.push_back(2);myInt.push_back(3);
for(auto iter = myInt.begin();
iter != myInt.end();
++iter)
{
if(*iter == 1)
{
iter = myInt.erase(iter);
if(iter != myInt.begin())
{
iter = std::prev(iter);
continue;
}
}
std::cout << *iter << std::endl;
}
}
But doing an erase inside of a iterator loop is frowned upon because it invalidates the old iterator and that could cause a lot of issues if you didn't plan for them.
erasing will invalidate the iterator
Iterator validity
Iterators, pointers and references pointing to position (or first) and beyond are
invalidated, with all iterators, pointers and references to elements before position (or
first) are guaranteed to keep referring to the same elements they were referring to
before the call.
You can't trivially erase an item from a collection while iterating over it. Think a little about it, your removing what itVec "points" to, after the removal itVec no longer "points" to an element, so it no longer have a "next" pointer.
If you check e.g. this reference, you will see that the erase function returns an iterator to the next element. Continue the loop with this one (without increasing it of course).
Consider either using a different collection class than vector or creating a new vector with the desired items removed rather than removing from existing vector.

std::insert_iterator and iterator invalidation

I tried writing a generic, in place, intersperse function. The function should intersperse a given element into a sequence of elements.
#include <vector>
#include <list>
#include <algorithm>
#include <iostream>
template<typename ForwardIterator, typename InserterFunc>
void intersperse(ForwardIterator begin, ForwardIterator end, InserterFunc ins,
// we cannot use rvalue references here,
// maybe taking by value and letting users feed in std::ref would be smarter
const ForwardIterator::value_type& elem) {
if(begin == end) return;
while(++begin != end) {
// bugfix would be something like:
// begin = (ins(begin) = elem); // insert_iterator is convertible to a normal iterator
// or
// begin = (ins(begin) = elem).iterator(); // get the iterator to the last inserted element
// begin now points to the inserted element and we need to
// increment the iterator once again, which is safe
// ++begin;
ins(begin) = elem;
}
}
int main()
{
typedef std::list<int> container;
// as expected tumbles, falls over and goes up in flames with:
// typedef std::vector<int> container;
typedef container::iterator iterator;
container v{1,2,3,4};
intersperse(v.begin(), v.end(),
[&v](iterator it) { return std::inserter(v, it); },
23);
for(auto x : v)
std::cout << x << std::endl;
return 0;
}
The example works only for containers that do not invalidate their
iterators on insertion. Should I simply get rid of the iterators and
accept a container as the argument or am I missing something about
insert_iterator that makes this kind of usage possible?
The example works only for containers that do not invalidate their iterators on insertion.
Exactly.
Should I simply get rid of the iterators and accept a container as the argument
That would be one possibility. Another would be not making the algorithm in-place (ie. output to a different container/output-iterator).
am I missing something about insert_iterator that makes this kind of usage possible?
No. insert_iterator is meant for repeated inserts to a single place of a container eg. by a transform algorithm.
The problems with your implementation have absolutely nothing to do with the properties of insert_iterator. All kinds of insert iterators in C++ standard library are guaranteed to remain valid, even if you perform insertion into a container that potentially invalidates iterators on insert. This is, of course, true only if all insertions are performed through only through the insert iterator.
In other words, the implementation of insert iterators guarantees that the iterator will automatically "heal" itself, even if the insertion lead to a potentially iterator-invalidating event in the container.
The problem with your code is that begin and end iterators can potentially get invalidated by insertion into certain container types. It is begin and end that you need to worry about in your code, not the insert iterator.
Meanwhile, you do it completely backwards for some reason. You seem to care about refreshing the insert iterator (which is completely unnecessary), while completely ignoring begin and end.

What's wrong with my vector<T>::erase here?

I have two vector<T> in my program, called active and non_active respectively. This refers to the objects it contains, as to whether they are in use or not.
I have some code that loops the active vector and checks for any objects that might have gone non active. I add these to a temp_list inside the loop.
Then after the loop, I take my temp_list and do non_active.insert of all elements in the temp_list.
After that, I do call erase on my active vector and pass it the temp_list to erase.
For some reason, however, the erase crashes.
This is the code:
non_active.insert(non_active.begin(), temp_list.begin(), temp_list.end());
active.erase(temp_list.begin(), temp_list.end());
I get this assertion:
Expression:("_Pvector == NULL || (((_Myvec*)_Pvector)->_Myfirst <= _Ptr && _Ptr <= ((_Myvect*)_Pvector)->_Mylast)",0)
I've looked online and seen that there is a erase-remove idiom, however not sure how I'd apply that to a removing a range of elements from a vector<T>
I'm not using C++11.
erase expects a range of iterators passed to it that lie within the current vector. You cannot pass iterators obtained from a different vector to erase.
Here is a possible, but inefficient, C++11 solution supported by lambdas:
active.erase(std::remove_if(active.begin(), active.end(), [](const T& x)
{
return std::find(temp_list.begin(), temp_list.end(), x) != temp_list.end();
}), active.end());
And here is the equivalent C++03 solution without the lambda:
template<typename Container>
class element_of
{
Container& container;
element_of(Container& container) : container(container) {}
public:
template<typename T>
bool operator()(const T& x) const
{
return std::find(container.begin(), container.end(), x)
!= container.end();
}
};
// ...
active.erase(std::remove_if(active.begin(), active.end(),
element_of<std::vector<T> >(temp_list)),
active.end());
If you replace temp_list with a std::set and the std::find_if with a find member function call on the set, the performance should be acceptable.
The erase method is intended to accept iterators to the same container object. You're trying to pass in iterators to temp_list to use to erase elements from active which is not allowed for good reasons, as a Sequence's range erase method is intended to specify a range in that Sequence to remove. It's important that the iterators are in that sequence because otherwise we're specifying a range of values to erase rather than a range within the same container which is a much more costly operation.
The type of logic you're trying to perform suggests to me that a set or list might be better suited for the purpose. That is, you're trying to erase various elements from the middle of a container that match a certain condition and transfer them to another container, and you could eliminate the need for temp_list this way.
With list, for example, it could be as easy as this:
for (ActiveList::iterator it = active.begin(); it != active.end();)
{
if (it->no_longer_active())
{
inactive.push_back(*it);
it = active.erase(it);
}
else
++it;
}
However, sometimes vector can outperform these solutions, and maybe you have need for vector for other reasons (like ensuring contiguous memory). In that case, std::remove_if is your best bet.
Example:
bool not_active(const YourObjectType& obj);
active_list.erase(
remove_if(active_list.begin(), active_list.end(), not_active),
active_list.end());
More info on this can be found under the topic, 'erase-remove idiom' and you may need predicate function objects depending on what external states are required to determine if an object is no longer active.
You can actually make the erase/remove idiom usable for your case. You just need to move the value over to the other container before std::remove_if possibly shuffles it around: in the predicate.
template<class OutIt, class Pred>
struct copy_if_predicate{
copy_if_predicate(OutIt dest, Pred p)
: dest(dest), pred(p) {}
template<class T>
bool operator()(T const& v){
if(pred(v)){
*dest++ = v;
return true;
}
return false;
}
OutIt dest;
Pred pred;
};
template<class OutIt, class Pred>
copy_if_predicate<OutIt,Pred> copy_if_pred(OutIt dest, Pred pred){
return copy_if_predicate<OutIt,Pred>(dest,pred);
}
Live example on Ideone. (I directly used bools to make the code shorter, not bothering with output and the likes.)
The function std::vector::erase requires the iterators to be iterators into this vector, but you are passing iterators from temp_list. You cannot erase elements from a container that are in a completely different container.
active.erase(temp_list.begin(), temp_list.end());
You try to erase elements from one list, but you use iterators for second list. First list iterators aren't the same, like in second list.
I would like to suggest that this is an example of where std::list should be used. You can splice members from one list to another. Look at std::list::splice()for this.
Do you need random access? If not then you don't need a std::vector.
Note that with list, when you splice, your iterators, and references to the objects in the list remain valid.
If you don't mind making the implementation "intrusive", your objects can contain their own iterator value, so they know where they are. Then when they change state, they can automate their own "moving" from one list to the other, and you don't need to transverse the whole list for them. (If you want this sweep to happen later, you can get them to "register" themselves for later moving).
I will write an algorithm here now to run through one collection and if a condition exists, it will effect a std::remove_if but at the same time will copy the element into your "inserter".
//fwd iterator must be writable
template< typename FwdIterator, typename InputIterator, typename Pred >
FwdIterator copy_and_remove_if( FwdIterator inp, FwdIterator end, InputIterator outp, Pred pred )
{
for( FwdIterator test = inp; test != end; ++test )
{
if( pred(*test) ) // insert
{
*outp = *test;
++outp;
}
else // keep
{
if( test != inp )
{
*inp = *test;
}
++inp;
}
}
return inp;
}
This is a bit like std::remove_if but will copy the ones being removed into an alternative collection. You would invoke it like this (for a vector) where isInactive is a valid predicate that indicates it should be moved.
active.erase( copy_and_remove_if( active.begin(), active.end(), std::back_inserter(inactive), isInactive ), active.end() );
The iterators you pass to erase() should point into the vector itself; the assertion is telling you that they don't. This version of erase() is for erasing a range out of the vector.
You need to iterate over temp_list yourself and call active.erase() on the result of dereferencing the iterator at each step.