This question already has answers here:
index or position in std::set
(3 answers)
Closed 6 years ago.
This question applies to both std::set and std::unsorted_set.
I have an iterator to an element in a set. I'd like to use the iterator to get an "index" for the element based on its location in the set.
For example, the indices for my set would be as follows:
int index = 0;
for(MySetType::iterator begin = mySet.begin(); begin != mySet.end(); begin++)
{
cout << "The index for this element is " << index;
index++;
}
I have tried doing arithmetic using iterators but it doesn't work:
int index = mySetIterator - mySet.begin();
Is there any way to use the iterator to get an index value like this based on its location in the set?
Use STL distance, namely std::distance(set.begin(), mySetIterator)
Please note that:
Returns the number of elements between first and last. The behavior
is undefined if last is not reachable from first by (possibly
repeatedly) incrementing first.
Remark : Complexity is linear;
However, if InputIt additionally meets the requirements of
LegacyRandomAccessIterator, complexity is constant.
std::set and set::unordered_set are associative containers, not sequence containers, hence the concept itself of index doesn't make much sense.
If you need to retrieve an index for an associative container then design should be changed (even because without a concept of least or most recent inserted element the indices in such containers are subject to change).
std::set has just a bidirectional iterator, which means you can't do what you're trying to do with operator + (or -). Those are only available to random access iterators, like std::vector provides.
You need to use std::distance to get the "index", and std::advance to move from the beginning of the set to the end.
auto distance = std::distance(mySet.begin(), someIterator);
auto it = mySet.begin();
std::advance(it, distance);
assert(it == someIterator);
Related
This question already has answers here:
How do I get the index of an iterator of an std::vector?
(9 answers)
Closed 2 years ago.
I found some c++ code that I would like to understand. In this code they use
int airplane = min_element(min_cost_airplane.begin(),
min_cost_airplane.end()) - min_cost_airplane.begin();
But I don't know what this line of code exactly accomplishes. min_cost_airplane is a vector. I understand the min_element function, but I can't wrap my head around the -vector.begin at the end. Is the structure of this line of code common used? The thing I understand is that this line of code returns an iterator to the smallest element in the vector minus an iterator to the first element of the vector. So what does the iterator point to?
Can someone please help me?
The std::min_element algorithm returns an iterator. You can dereference that iterator to access the "minimum" element of the container. If, instead, you want to know the index of the element you need to compute it as the distance from the beginning of the container.
For random-access iterators you can subtract the iterators to get the offset, or index value. That's what your example does. There's also a std::distance function that computes the index but it also works for non-random access iterators. For example:
auto iter = std::min_element(min_cost_airplane.begin(), min_cost_airplane.end());
int index = std::distance(min_cost_airplane.begin(), iter);
std::min_element returns an iterator to the first instance of the minimum value in the given range.
begin returns an iterator to the first element in the container.
The std::vector iterators are special (it's a random access iterator) in that you can subtract one from another which yields the distance between them in terms of elements (it's pointer arithmetic under the hood). To be more generic and clearer, write
auto airplane = std::distance(
min_cost_airplane.begin(),
std::min_element(min_cost_airplane.begin(), min_cost_airplane.end())
);
std::min_element is used to finds the smallest element in the range [first, last].
std::vector<int> v{3, 8, 4, 2, 5, 9};
std::vector<int>::iterator result = std::min_element(v.begin(), v.end());
//result iterator to the minimum value in vector v.
std::cout << "min element is: " << *result;
output: min element is: 2
Note : The smallest value in vector v is 2.
Say I have the following code:
typedef std::map< int, std::string >::iterator Iterator;
Iterator iter = myMap.begin();
while (iter != myMap.end())
{
Iterator current = iter;
++iter;
maybeDeleteElement( current ) // may call erase.
}
Given that std::map is implemented as a red-black tree, is it guaranteed that every element in the map will be visited exactly once? Or will modifying the map cause the tree to rebalance, and thus the iteration sequence to change?
Note: This is not a question about whether or not any iterators will be invalidated. But an iterator remaining valid does not necessarily mean that incrementing it will give you the same next element that it did before.
In a std::map the elements will be visited in order.
If you store an iterator that refers to an element that is not deleted, and hence not invalidated, the iterator will still refer to that same element. (If it was the end iterator, it remains the end iterator, as it is not invalidated).
When you advance that iterator, it will advance to the next element in order after the element you refer to.
For your particular example, yes, every element will be visited exactly once, because all deletion of elements was elements that are before the current iterator state of your loop.
If you insert elements ahead of whatever iterator you are using to iterate, then you'll eventually reach them as you iterate forward with your iterator. If you delete elements ahead of whatever iterator you are using to iterate, then they are no longer part of the future elements you'll reach if you iterate with that iterator.
If you insert or delete elements that are before the current location of the iterator, unless you start calling -- or similar functions, your current iteration will continue without noticing that they went away.
This is because ++ on a valid iterator in an ordered container is guaranteed to return the next element in the order, and operations on other iterators that do not invalidate an iterator don't change that iterator's invariants (like what element they refer to).
Yes, inserting/erasing can modify the iteration sequence. It does not happen in your example, as you erase iterators you've already passed by, but if you erase/insert elements that are positioned ahead of your current iterator, then it will modify the rest of the sequence.
Here is a short code which displays such behavior:
int main (){
map<int,int> mapa;
for(int i = 0; i < 5; ++i) mapa[i] = i;
bool add = false;
for(auto it = mapa.begin(); it != mapa.end(); ++it){
int x = it->second;
printf("%d\n", x);
if(add) mapa.erase(x+1);
add = !add;
}
return 0;
}
The example above will print 0 1 3 4 (instead of 0 1 2 3 4). Additionally, if you erase the current iterator, its reference to the next element will be invalidated and your program will crash at the next iteration.
Alternatively, you can also test the insertion, by substituting the if(add) above with:
if(add) mapa[x+5] = x+5;
else mapa[x-20] = x-20;
The example will print the extra elements {6, 8, 11, 16}, and not print the negative ones, since those are being inserted in a position prior to your current one.
Erasing an element from a map does not invalidate iterators, so I would expect it to continue to iterate properly.
See https://stackoverflow.com/a/6438087/5987
Yes, the iteration sequence changes. This is due to §23.2.4.1/10 and /11:
(p10) The fundamental property of iterators of associative containers is that they iterate through the containers in the non-descending order of keys where non-descending is defined by the comparison that was used to construct them. For any two dereferenceable iterators i and j such that distance from i to j is positive,
value_comp(*j, *i) == false
(p11)
For associative containers with unique keys the stronger condition holds,
value_comp(*i, *j) != false.
If, after an insert, the new element were added at the beginning of the iteration sequence regardless of the ordering of elements (so as to not to modify the sequence ahead of any existing iterator position), the above requirement would be violated.
In spite of your note, for the erase case this question is exactly about iterator invalidation, because if no iterator is invalidated, the order necessarily remains the same. This is because map is a sorted container, so no matter what internal representation changes may happen, the iteration order has to remain exactly the same.
To address specifically your example, it will traverse each element exactly once, because you save off the iterator to check and increment your traversal iterator.
In the case of insertion, if you insert before the current point of iteration that element won't be visited. Inserting after the current iterator will result in the new item being traversed.
map <int, string> rollCallRegister;
map <int, string> :: iterator rollCallRegisterIter;
map <int, string> :: iterator temporaryRollCallRegisterIter;
rollCallRegisterIter = rollCallRegister.begin ();
tempRollCallRegisterIter = rollCallRegister.insert (rollCallRegisterIter, pair <int, string> (55, "swati"));
rollCallRegisterIter++;
tempRollCallRegisterIter = rollCallRegister.insert (rollCallRegisterIter, pair <int, string> (44, "shweta"));
rollCallRegisterIter++;
tempRollCallRegisterIter = rollCallRegister.insert (rollCallRegisterIter, pair <int, string> (33, "sindhu"));
// Displaying contents of this map.
cout << "\n\nrollCallRegister contains:\n";
for (rollCallRegisterIter = rollCallRegister.begin(); rollCallRegisterIter != rollCallRegister.end(); ++rollCallRegisterIter)
{
cout << (*rollCallRegisterIter).first << " => " << (*rollCallRegisterIter).second << endl;
}
Output:
rollCallRegister contains:
33 => sindhu
44 => shweta
55 => swati
I have incremented the iterator. Why is it still getting sorted? And if the position is supposed to be changed by the map on its own, then what's the purpose of providing an iterator?
Because std::map is a sorted associative container.
In a map, the key value is generally used to uniquely identify the element, while the mapped value is some sort of value associated to this key.
According to here position parameter is
the position of the first element to be compared for the insertion
operation. Notice that this does not force the new element to be in
that position within the map container (elements in a set always
follow a specific ordering), but this is actually an indication of a
possible insertion position in the container that, if set to the
element that precedes the actual location where the element is
inserted, makes for a very efficient insertion operation. iterator is
a member type, defined as a bidirectional iterator type.
So the purpose of this parameter is mainly slightly increasing the insertion speed by narrowing the range of elements.
You can use std::vector<std::pair<int,std::string>> if the order of insertion is important.
The interface is indeed slightly confusing, because it looks very much like std::vector<int>::insert (for example) and yet does not produce the same effect...
For associative containers, such as set, map and the new unordered_set and co, you completely relinquish the control over the order of the elements (as seen by iterating over the container). In exchange for this loss of control, you gain efficient look-up.
It would not make sense to suddenly give you control over the insertion, as it would let you break invariants of the container, and you would lose the efficient look-up that is the reason to use such containers in the first place.
And thus insert(It position, value_type&& value) does not insert at said position...
However this gives us some room for optimization: when inserting an element in an associative container, a look-up need to be performed to locate where to insert this element. By letting you specify a hint, you are given an opportunity to help the container speed up the process.
This can be illustrated for a simple example: suppose that you receive elements already sorted by way of some interface, it would be wasteful not to use this information!
template <typename Key, typename Value, typename InputStream>
void insert(std::map<Key, Value>& m, InputStream& s) {
typename std::map<Key, Value>::iterator it = m.begin();
for (; s; ++s) {
it = m.insert(it, *s).first;
}
}
Some of the items might not be well sorted, but it does not matter, if two consecutive items are in the right order, then we will gain, otherwise... we'll just perform as usual.
The map is always sorted, but you give a "hint" as to where the element may go as an optimisation.
The insertion is O(log N) but if you are able to successfully tell the container where it goes, it is constant time.
Thus if you are creating a large container of already-sorted values, then each value will get inserted at the end, although the tree will need rebalancing quite a few times.
As sad_man says, it's associative. If you set a value with an existing key, then you overwrite the previous value.
Now the iterators are necessary because you don't know what the keys are, usually.
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.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Can you remove elements from a std::list while iterating through it?
I have a loop in a function, that iterates over an std::list from begin to end.
In each cycle, I perform some checks and maybe do a few manipulations on the current list entry, and in some cases I want to remove it from the list.
Now, as expected my iterator gets invalidated.
Is there any way to work around this, to remove elements from a list while iterating over it?
Catch the return value of erase and use it as your iterator. The return value is an iterator to the next valid location after the erasure.
if(ShouldErase)
{
iter = list.erase(iter);
}
else
{
++iter;
}
Reference
Excerpt:
Return value
A bidirectional iterator pointing to the new location of the element that followed the last element erased by the function call, which is the list end if the operation erased the last element in the sequence.
Use postfix increment.
list.erase(it++);
it is increased, so it no longer refers to the erased element, then the previous value of it is given to list.erase. Make sure that you either do list.erase(it++) or ++it in your loop - doing both will skip elements and potentially increment past end of the list.
Have you considered using the list::remove_if algorithm?