c++: popping an element by key out of an std::map - c++

I'm interested in removing an element with a specific key out of a map and use this element.
Something that will look like:
itr = MyMap.pop(wantedKey);
//Now MyMap is missing the element which has the key 'wantedKey'.
//Do something with this element through 'itr'.
Is there an stl map method for doing this?
EDIT
Following carleeto's response, I want to clarify: What I need is the element being removed from the map and the program being able to use it afterwards, it could be the element itself as a pair, not necessarily an iterator.

There are two options: use it in-place then remove it, or move it to a local variable, remove the entry, then use it.
// use-remove
auto i = MyMap.find(wantedKey);
if (i != MyMap.end()) {
// use-remove
use(i->second);
MyMap.erase(i);
// or
// move-remove-use
auto x = std::move(i->second);
MyMap.erase(i);
use(x);
} else {
// Not found
}

Not that I know of, but you can use std::map::find to get an iterator and then call std::map::erase with said iterator as an argument when you're done.

From your variable naming, I think you might be confusing concepts here.
itr = MyMap.pop(wantedKey);
//Do something with this element through 'itr'.
Iterators only point to elements in containers. Therefore, if you had received an iterator through a function called pop (even if it existed), the iterator would reference not the element you popped, but probably the one after or before it, like std::vector::erase. This is because the purpose of an iterator is to iterate over the elements in a container. Therefore, if an element is not in the container, you cannot get an iterator to it. However, even if you used the iterator returned by the erase function, it would not reference you would be expecting it to.
So you can erase an element from the map, like so many have pointed out, by searching for it, getting the ierator to it and then calling erase with that iterator. but you cannot get an iterator that points to element you have erased. Hope this clears things up.
UPDATE: If all you want is to access the element and use it, then all you need to do use std::map::find to get an iterator and std::map::erase to remove the item from the map, once you have finished using the iterator. The reason is that even if you have stored a copy of the iterator for future use, once you call erase, it will be invalidated. To be able to access it after you have erased it, depending on scope, you will probably need to copy it.
Finally, what you want to do is a very common task - look up a map based on a key and perform an operation on the associated element. It's quite likely that you have a list of keys to go through. You should also look up functors, std::for_each and std::transform. I realise this is not operating on the element after you have removed it, but I thought I would add it in, seeing as how its a related operation. For example: You could move all elements that match a list of keys into another container (say, a vector, and then use the above to operate on them).

Probably what you want to do is
itr = MyMap.find('thing in a string');
to find the iterator and then use it,
MyMap.erase(itr)
And then erase it.

Pop() belongs to the stack datastructure. To access an element of a map, use the [] operator (http://www.cplusplus.com/reference/map/map/operator%5B%5D/), to remove it from the map use (http://www.cplusplus.com/reference/map/map/erase/).

itr = MyMap.find(wantedKey);
if(itr != MyMap.end()) {
use( itr->second );
MyMap.erase(itr);
}
your.uncle = bob;

Using C++'s std::map<T, U>::find():
map.erase(map.find(key));

The way I did it is below. In my case the map stores std::shared_ptr values, making the copy cheap(ish), and the object ownership transferral clear.
auto it = MyMap.find( wantedkey );
if ( it == MyMap.end() ) throw runtime_error("not found");
auto ret = it->second; // make copy of shared_ptr
MyMap.erase(it);
return ret;
The caller gets a shared_ptr with a reference count of at least one (from the copy). Note the function must return the shared_ptr by value, etc.

Related

Can I get a container object from an iterator?

std::vector<int> vec={1,2,3};
std::vector<int>::iterator it = vec.begin();
if(vec == get_vec_from_it(it)){
puts('sucesss');
}
std::vector<int> get_vec_from_it(std::vector<int>::iterator it){
/*?*/
}
How should I write get_vec_from_it function in the above example?
The basic idea is that iterators abstract away where the elements come from, there might not even be a container. Afaik there is a single type of iterator that "knows" its container and that is std::back_insert_iterator, though thats an exception. The container member is only protected so there is even a way to get the container from a std::back_insert_iterator, but thats not how it is meant to be used.
You can adance the iterator to get the next element, but you wouldn't know where to stop, because at some point you'll reach the end of the vector and there is no way to identify it. If you pass begin and end you can create a copy of the original vector:
std::vector<int> get_vec_from_it(std::vector<int>::iterator begin ,std::vector<int>::iterator end){
return {begin,end};
}
Though, thats just a different way to copy the vector and you need to know both begin and end.
I made a function that returns the iterator that points to the node but I couldn't write the stop condition in a for statement. So I wonder if the stop condition can be written like it!=get_vec_from_it(it).end()
Functions that work on a range of elements typically take a pair of iterators, first and last, to know where to stop (alternatively a first iterator and number of elements can be used). Your idea of using it!=get_vec_from_it(it).end() is overcomplicating the issue. Just pass vec.end() to the function and use that: it != end.
No.
You can create a vector from a pair of iterators, or an iterator and number of elements. Example:
std::vector<int>
get_vec_from_its(std::vector<int>::iterator first, std::vector<int>::iterator last){
return std::vector<int>(first, last);
}
// ...
if(vec == get_vec_from_it(vec.begin(), vec.end())){
The function is of course so trivial that I would recommend instead to use the constructor directly.

C++ loop on map not detecting change of map`s end

I am having a problem while looping thru a map (std::map).
Inside my loop, there is a call to a function which sometimes (not always) erases elements of this same map. After this function is used, there is some code which is using some of this map information as input.
I am having no problems after this function erases any elements, except on the unique case that the last element of the map is erased.
My loop semms not to understand that the last element of the map is not the same as when it started to operate, and will try to operate on elements which doesnt exist, creating a crash.
It seems to me that the myMap.end() call on the loop description is not able to update itself with the new end() of the map.
The relevant part of the code is listed below:
for(std::map<int, ConnectionInfo>::iterator kv = myMap.begin(); kv != myMap.end(); ++kv) {
int thisConnectionID=kv->first; //This is where I get garbage when the loop enters when it shouldnt;
ConnectionInfo currentConnectionInfo=kv->second; //This is where I get garbage when the loop enters when it shouldnt;
status=eraseSomeMapElementsIfNecessary(thisConnectionID,currentConnectionInfo.DownPacket); //this function might erase elements on myMap. This generates no problems afterwards, except when the end element of myMap is erased
... //Next parts of the code make no further usage of myMaps, so I just hid it not to pollute the code
}
Is my interpretation that the kv != myMap.end() is not being able to understand that the inner loop is changing (erasing) the last element (end) of myMap?
In this case, how can I fix this issue?
Or is my interpretation wrong and the solution has nothing to do with what I stated before?
Thanks for your help!
The usual idiom when iterating a map with possibly deleting element is:
for(auto it = map.begin(); it != map.end(); ) {
if ( *it == /*is to delete*/ ) {
it = map.erase(it);
}
else
++it;
}
if your eraseSomeMapElementsIfNecessary might erase some random values in map being iterated then this will for sure cause problems. If element to which it is referencing was erased, it becomes invalid, then incrementing it with ++it is also invalid.
The problem is actually only with the it iterator, if eraseSomeMapElementsIfNecessary erases it and then you use it - you have Undefined Behaviour (UB). So the solution is to pass current iterator to eraseSomeMapElementsIfNecessary, and return from it the next one to iterate:
it = eraseSomeMapElementsIfNecessary(it);
the body of the for loop from my example should be inside your eraseSomeMapElementsIfNecessary function. At least this is one solution.
I am having no problems after this function erases any elements, except on the unique case that the last element of the map is erased.
Erasing an element in any container invalidates the iterator to it. After that you increment the invalidated iterator.
You should increment the iterator before you delete the element pointed by it.
If you do not know what elements that function inside the loop erases assume that all iterators are invalidated.
Maybe these 2 links will help:
How can I delete elements of a std::map with an iterator?
https://stackoverflow.com/a/8234813/3464942
Basically, what it all boils down to, is that you must update the iterator before it becomes invalid.
You have to preserve the next iterator before erasing the current one; since the current one will be invalid after deleting the element.
auto nextit = it+1;
map.erase(it);
it = nextit;

stl : map of vectors - begin() address

I've been fighting for several hours to understand why begin() of vector within a map doesn't return the same address if I point to the vector.begin() itself or the second.begin() member of the map.
Let me explain:
I have a class containing a map which key is an int and its content a vector of int. I have to go through the map and keep in memory the position where I was 'just before', so I also have another map of iterators (of the first map).
So, I would like to get something like:
map1 : <2,<3,8,1,3,7,1>>
map2 : <8,<6,9,1,3>>
map3 : <1,<3,1>>
etc.
To make it simpler, in my code, the T_IPC_CommandId is just an enum of int.
I have a method called Add(int TopCommand, int Command) which fills/creates the map of vectors of commands. For example, to create map3, I will code:
Add(1,3), Add(1,1)
I have a method called GetNext(int Command) which returns the next int, in the map pointed by Command. For example in map2, calling Get(8) will return 6, and the next call will return 9, then 1, 3 and 0 for the next calls.
In order to know which int I will return, I need to keep track, for each map, what is the next int to return. So, I use a IPC_CommandId_Pointer which is a map which key is the command itself and holds the iterator where I am in the map. And here is the problem:
At each Add() call, I initialise this IPC_CommandId_Pointer to the beginning of main map. Like this:
void T_ListOfCommand::Add(T_IPC_CommandId Top_CommandId, T_IPC_CommandId IPC_CommandId)
{
T_IPC_CommandId_Vec_Iter Vec_Iter;
T_IPC_CommandId_Vec Vec;
if ((IPC_CommandId_Map.find(Top_CommandId)) == IPC_CommandId_Map.end())
{
IPC_CommandId_Map[Top_CommandId].clear();
}
IPC_CommandId_Map[Top_CommandId].push_back(IPC_CommandId);
// Repeated at each add, but don't care ...
Vec_Iter = IPC_CommandId_Map[Top_CommandId].begin();
IPC_CommandId_Pointer[Top_CommandId] = Vec_Iter;
}
The problem I have is that, at each IPC_CommandId_Map[Top_CommandId].push_back(IPC_CommandId), the IPC_CommandId_Map[Top_CommandId].begin() doesn't return the same address.
Now, when replacing the:
Vec_Iter = IPC_CommandId_Map[Top_CommandId].begin();
IPC_CommandId_Pointer[Top_CommandId] = Vec_Iter;
With:
Vec = IPC_CommandId_Map[Top_CommandId];
IPC_CommandId_Pointer[Top_CommandId] = Vec.begin();
This works fine.
I'm supposing to point to the same location when using the begin() in the map, or when dereferencing the vector, isn't it? Well, it seems not.
If someone could explain me the difference, that would be great.
vector::begin() is not guaranteed to return the same address each time!
If std::vector::push_back() causes reallocation of the vectors data (since its data has to be continuous), the iterator (address) which begin() returns will be different.
And (as The Paramagnetic Croissant stated in the comment) the code Vec = IPC_CommandId_Map[Top_CommandId]; copies the element of the map to the variable Vec.
(Vocabulary check: you're not dereferencing anything, particularly not a vector.)
Vec_Iter = IPC_CommandId_Map[Top_CommandId].begin();
is an iterator into the vector in the map.
It becomes invalid when the vector is reallocated.
Every time the vector is reallocated you will get a different value for begin().
It's a bad idea to store this iterator across calls to push_back or anything else that may invalidate an iterator.
Vec = IPC_CommandId_Map[Top_CommandId];
IPC_CommandId_Pointer[Top_CommandId] = Vec.begin();
is an iterator into the local copy Vec.
It becomes invalid as soon as the function returns.
It's a bad idea to store this iterator anywhere beyond Vec's lifetime.
Overall, iterators should be considered transient and only be used in as small a scope as possible.
A much more robust solution is to store a "current index" for the vector instead of an iterator.

C++, error when using iterator for a map container that is pointed to. map/set iterator not incrementable

I am getting this error when trying to iterate over a map that is pointed to by another object. It works when I am not using a pointer. (Iterating over the member map "pieces") I am therefore wondering what to do, or if it's not possible to iterate through the map like this ? :
Board * Board::ccBoard(){
Board * newBoard = new Board();
map<Vec2, Piece>::iterator it;
for (it = newBoard->pieces.begin(); it != newBoard->pieces.end(); ++it)
newBoard->removePiece(it->first);
return newBoard;
}
Thanks in advance!
The removePiece() function removes the element that it is referring to, invalidating it. An attempt is then made to increment it resulting in the assertion failure. From map::erase():
References and iterators to the erased elements are invalidated.
I am unsure what the intention of the for loop is, it appears that it would effectively empty the map in which case just use map::clear():
newBoard->pieces.clear();
To fix, get rid of the ++it in the for loop and replace it->first by it++->first.
(This will increment the iterator and call erase() using a copy.)

STL Vector, Iterator and Insert (C++)

I have a method to which a vector's iterator is passed.
In this method I'd like to add some elements into the vector, but I am not sure whether this is possible when having only the iterator
void GUIComponentText::AddAttributes(vector<GUIComponentAttribute*>::iterator begin, vector<GUIComponentAttribute*>::iterator end)
{
for (vector<GUIComponentAttribute*>::iterator i = begin; i != end; ++i)
{
GUIComponentAttribute &attrib = *(*i);
// Here are the GUIComponentAttribute objects analyzed - if an object of a
// special kind appears, I would like to add some elements to the vector
}
}
Thanks
Markus
In the code you show, this is not possible. Especially because you should not add/remove elements to/from a vector while you iterate over it.
This is a long standing design "issue" in the STL. Iterators do not allow the modification of the structure of the underlying sequence they are iterating over: ie you can modify (sometimes) the elements themselves, but you cannot add/remove elements. Though InputIterator and OutputIterator are a bit special in this regard... hum...
This is actually the cause of the erase/remove idiom:
vec.erase(std::remove_if(vec.begin(), vec.end(), predicate), vec.end());
So, no, sorry, there is no way to actually modify the vector.
However, as exposed above, you can perfectly use the remove_if algorithm and simply return the new end of the valid range... or you can ask for the whole vector to begin with.
As noted by Björn, modifying a sequence structure while iterating over it is error-prone.
First, you'll have to change the interface. Given two iterators,
there's no way to get back to the container to which they refer; so if
you want to modify the container, you'll have to pass a reference to it,
i.e.:
void GUIComponentText::AddAttributes(
std::vector<GUIComponentAttribute*>& attributes )
{
for ( std::vector<GUIComponentAttribute*>::iter = attributes.begin();
iter != attributes.end();
++ iter )
{
// ...
}
}
Having done that: insertion can invalidate iterators. So it depends on
where you want to insert. If you want to insert at the current
position: std::vector<>::insert of a single element returns an
iterator to that element, which was inserted before your element, so you
can assign it to your iterator, adjust (if necessary), and continue:
iter = attributes.insert(iter, newAttribute);
++ iter; // Return to where we were...
If you're appending (push_back), the problem is a bit more complex;
you need to calculate the offset, then reconstruct the iterator:
size_t offset = iter - attributes.begin();
attributes.push_back( nweAttribute );
iter = attributes.begin() + offset;
In this case, it is probably simpler to iterate using a size_t and
[], rather than an iterator.
It is not possible to add elements into a vector whilst iterating over it. In addition, you most certainly cannot add one to a vector with just a pair of iterators- you'd need a pointer/reference to the whole vector object.
The best you could do is return a vector of new components to add by the the calling function.