Based on reading https://en.cppreference.com/w/cpp/container/unordered_set/emplace, it states: Rehashing occurs only if the new number of elements is greater than max_load_factor()*bucket_count().
In this code:
int main()
{
unordered_set<int> myset;
myset.emplace(4);
cout << myset.bucket_count() << endl;
cout << myset.max_load_factor() << endl;
auto it = myset.begin();
myset.emplace(3);
cout << (it == myset.begin()) << endl;
}
There is no rehashing when I do emplace the second time (bucket count is 2 and max load factor is 1 right before emplacing the second time, which is not more than the new number of elements, 2), but my begin() iterator changes. Why is this iterator invalidated/changed even though there is no rehashing?
As john already mentioned, your iterator is still valid but it points to the begin() of your unordered_set container at the moment where there was only one element '4'.
So, one thing you should keep in mind using C++ iterators - don't cache them at all! Unless you don't forget to update the iterator every time you do change the container invariants.
Related
I want to save an iterator of the first elements in a forward list as it and do some insert on the list. Then I want to erase the element at it.
For example, in {20,30,40,50} and insert 10 at front. We get {10,20,30,40,50}. Then I want to erase 20, which means I want {10,30,40,50}.
I tried to use before_begin() but it seems that it always point to the before of begin() of the list even after inserting.
#include <bits/stdc++.h>
using namespace std;
int main()
{
forward_list<int> fl = { 20, 30, 40, 50 };
auto it = fl.before_begin();
fl.emplace_front(10);
fl.erase_after(it);
cout << "Element of the list are:" << endl;
for (auto it = fl.begin(); it != fl.end(); ++it)
cout << *it << " ";
return 0;
}
Have 20,30,40,50 rather than 10,30,40,50.
Is there any way to store a inerator that after ++, it will point to the begin element.
I solve this problem by adding an count, counting the number inserted at front and then move iterators from begin().
This question already has answers here:
Iterator invalidation rules for C++ containers
(6 answers)
Closed 2 years ago.
A pointer to a vector should stay stable during the life time of the vector. A pointer to an element in the vector is subject to change as the vector grows to accomodate more elements (and therefore has to move its contents in memory). I.e. the "header" of the vector stays where it is in memory, the content may move.
I seem to have just confirmed this empirically, I just wanted to make sure that my concept of this was accurate.
Test code:
std::vector<std::string> vec {21, "Element"};
std::vector<std::string> *ptr_v = &vec;
std::string *ptr_el = &vec.at(20);
std::cout << "Before: &vec: " << &vec << " *vec: " <<
ptr_v << " *element 20:" << ptr_el << std::endl;
for(int i = 0; i < 100000; i++)
vec.push_back("Element");
std::vector<std::string> *ptr_v_a = &vec;
std::string *ptr_el_a = &vec.at(20);
std::cout << "After: &vec: " << &vec << " *vec: " <<
ptr_v_a << " *element 20:" << ptr_el_a << std::endl;
enter image description here
Yes you are right, pointers or iterators at vector elements are only valid/safe to use as long as you don't perform certain operations on the vector.
Adding new elements or using resize/reserve might cause the vector to move the elements to a different location because it needs more memory to grow in size. It doesn't always happen, when a vector needs to reallocate and move it's data to a new place, it actually allocates more memory than necessary to avoid having to do it every time you add a single new item. But you should never rely on this. Always expect your pointers/iterators to be invalid once you change the vectors size. Acessing them afterwards is undefined behaviour. For the same reason adding/removing elements while going through a vector with a ranged based loop is a very bad idea.
I am trying to remove all duplicates in a vector of sorted strings. However, I keep on getting errors and I cannot really figure out why. I believe it has something to do with the vector resizing while also being used in a loop. Here is the code:
auto it = name.begin() + 1;
int count = 1;
while(it != name.end())
{
if (*it == *(it - 1))
{
count++;
it++;
}
else if (*it != *(it - 1) && count > 1) {
it = name.erase(it - count , it - 1);
cout << *it << " occurs " << count << " times." << endl;
count == 1;
}
}
The code checks to see if the previous element is duplicated and then removes it. It is important to know how many times the element occured as I would have to. Any idea how to fix this?
After you erased duplicated elements the current element does not have the preceding element.
else if (*it != *(it - 1) && count > 1) {
it = name.erase(it - count , it - 1);
cout << *it << " occurs " << count << " times." << endl;
count == 1;
}
Thus in the next iteration expressions *it == *(it - 1) and *it != *(it - 1) are invalid.
Moreover it can occur such a way that all elements before name.end() will be equal each other. In this case nothing will be erased.
You can use standard algorithm std::unique declared in header <algorithm>.For example
#include <algorithm>
#include <vector>
#include <string>
//..
name.erase( std::unique( name.begin(), name.end() ), name.end() );
Any idea how to fix this?
Fun effort. I believe I have identified 2 interesting possibilities.
Your loop started at the beginning of the vector 'name'. This is fine. Now you need to find a mechanism to delay the vector::erase() of these duplicate elements. "vector::erase()" invalidates any iterator at or after that erase. It may be why vectors support operations at the back ... we can take advantage of this.
Found on the internet: Iterator validity
Iterators, pointers and references pointing to
position (or first) and beyond are invalidated,
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.
So, by working first at the back of the names vector, we can minimize pointer invalidations.
What I did, and suggest you try, is to add a second vector to hold the duplicate-element-iterators. I labeled mine dups, with the following typedef's
typedef std::vector<std::string> Names;
typedef Names::iterator NamesIT;
// ---
typedef std::vector<NamesIT> NamesItVec;
vector<NamesItVec> dups.
When you have identified all the duplicates, and have push_back()'d the names-iterators into dups, you can then spin through dups, at that time erasing the duplicate name-elements.
Note that when you erase from the back of a vector, the erase() has an easier time of its re-arrangement in memory. (An effective 'second-use' of the earlier sorting effort.)
It looks like this:
std::cout << "\nremoving dups from vector name: back to front" << std::endl;
while (dups.size()) // as long as it has a iterator in it
{
auto dupIT = dups.back(); // reference closest element to strVec.end()
std::cout << "==> " << (*dupIT) << std::endl; // debug info
(void)names.erase(it); // erase the single element
dups.pop_back(); // remove the last element from dups
}
Hope this is not too much help. Good luck.
I'm currently trying to display the total no. of topicids and testids based on the name.
However I'm having trouble doing that display. I initially had a vector containing all the data.
For e.g.
user1:name:topic1:test1
user1:name:topic2:test1
user2:name:topic1:test1
user2:name:topic2:test1
Due to the multiple duplicates in the vector, I want to display in the following format:
username:name:numofTopics:numofTests
user1:name:2:2
user1:name:2:2
Therefore, i thought of comparing the name against the next name in the vector and push in the element to a new vector called singleAcc. The purpose of this is to display the duplicate element as ONE element.
Below is my code for displaying the data
vector<AccDetails> singleAcc;
for (vector<AccDetails>::iterator itr=accInfo.begin();itr!=accInfo.end()-1; ++itr) {
if (itr->name == itr[1].name) {
//cout << (*itr) << endl;
singleAcc.push_back(*itr);
}
}
for (vector<AccDetails>::iterator itr = singleAcc();itr!=singleAcc();++itr) {
cout << left
<< setfill(' ')
<< setw(20) << itr[0].username
<< setw(20) << itr[0].name
<< setw(20) << countTopics(itr->name)
<< setw(20) << countTests()
<< endl;
}
Problem:
On the first vector iteration, the name will not compare against the last element bcoz of accDetails.end()-1.
How to display the duplicate elements as ONE element? Is what I'm doing in the 2nd iteration the right thing?
Hope someone can help me with this. Or is there a better way to doing this?
Thanks!
Why this won't work
Your proposed solution simply won't work as intended. Consider three elements that are considered duplicates in consecutive subsequence (I am using numbers to simplify the concept):
[1,1,1]
The iterator will first compare 1 to 1, and then push_back the first one.
Then it will compare second 1 to the third one, which again returns true, and the result that was supposed to have no duplicates ends up:
[1,1]
So it's clearly not something you want to do. In general, it looks like a rather weird problem, but so solve this one part you've posted here, I suggest using std::multiset.
A better solution
Create a comparator that tests for the name field just like you do here.
Then, recovering unique fields is rather simple:
std::multiset<AccDetail> s;
for (auto element_it = s.begin(); element_it != s.end(); element_it = s.upper_bound(*element_it)) {
auto er = s.equal_range(*element_it);
// use this to get all the elements with given name
for (auto i = er.first; i != er.second; ++i)
cout << *i << " ";
// use this to get the number of them
cout << std::distance(er.first, er.second);
}
See a working sample on Coliru.
Bonus
You are right that the iterator in first loop would go over the bounds. The solution for that is rather simple: zip iterator which can handle this automatically.
for (auto elem_pair : zip(v, v | drop(1)))
if (elem_pair.first == elem_pair.second)
...
Boost.Range has tools that allow that code to work. It would still suffer from the problems I've mentioned, though.
For example map1 is gaving values 1 to 10 with some address(begin to end).
i want to have values 10 to 1 with corresponding address in map2(begin to end)
map<long , int* > v;
map<long , int* > rv;
int i,a[10];
for(i=0; i<10; i++)
{
a[i] = i+1;
v.insert(pair<long, int *>(i+1,&a[i]));
}
itr = v.begin();
while(itr != v.end())
{
cout << itr->first << " "<<itr->second;
cout << endl;
itr++;
}
rv.insert(v.rbegin(),v.rend());
cout << "copied array: "<<endl;
itr = rv.begin();
while(itr != rv.end())
{
cout << itr->first << " "<<itr->second;
cout << endl;
itr++;
}
i tried above one but am getting values 1 to 10 only..my expected values 10 to 1.
please help me to find out....
STL map is an ordered container. The order of items that you get during the iteration is independent of the order in which you insert the items into the container.
The order of iteration is determined by two things:
The value of the key, and
The Compare class passed as the template parameter to the map
You can iterate the map in reverse order (your code snippet shows that you already know how it is done). The performance penalty for reverse-iterating a map, if any, is negligible. You can also provide a non-default Compare (std::greater<long> instead of the default std::less<long>) to have the default order of iteration altered.
This is impossible because std::map is ordered associative container. If you want to preserve order of insertion use other containers such as std::list or std::vector.
Maps sort by increasing value (as dictated by operator< ), so no matter how you insert the elements, they will be returned in sorted order. You are certainly doing the insertion in reverse, but every element placed is being duly sorted into proper ascending order.