C++ - unordered_map operator [], unexpected behavior - c++

This is a simple script I was working on, but I failed to understand why it was behaving unexpectedly.
Basically, I had an array of integers with duplicates, and I wanted to store the number of times an element occurred in the array along with the element's value in an unordered_map,
Then, for each entry in the map {k, v}, I needed to determine if k + 1 existed in the array, and if so, do something with it.
Below you can see the code.
vector<int> A = {1, 1, 3, 2, 5, 3};
for (int i = 0; i < A.size(); ++i) m[A[i]]++;
int ans = 0;
for (const auto& e: m) {
if (m[e.first + 1] > 0) ans = max(ans, e.second + m[e.first + 1]);
}
Everything seemed to work. However, when k + 1 did not exist in the unordered_map, the loop would just terminate, and I do not understand why.
According to c++ documentation, the operator [] inserts a new element if it does not exist . But that does not tell me anything about the loop just not working.
I suspect it has something to do with the fact that I am modifying the unordered_map inside the loop. If this is the case, could you guys elaborate more on that?
I really appreciate your help.

Using m[e.first + 1] inside your loop will insert a new element into m if it does not exist, which will cause problems for the loop itself because a range-based for loop uses iterators internally, and altering a std::unordered_map while you are iterating through it with iterators is undefined behavior, as an insertion may invalidate the iterators:
If an insertion occurs and results in a rehashing of the container, all iterators are invalidated. Otherwise iterators are not affected. References are not invalidated. Rehashing occurs only if the new number of elements is greater than max_load_factor()*bucket_count().
To avoid that, use the map's find() method instead to check for a key's existence without inserting it:
for (const auto& e: m) {
auto iter = m.find(e.first + 1);
if (iter != m.end()) ans = max(ans, e.second + iter->second);
}

Related

Looping back near the beggining using std::vector iterators

I have an iterator that needs to loop near the beginning of the vector whenever it reaches its end, for the amount it went over the end, like so:
std::vector<int> vec = {...}, vec1;
std::vector<int>::iterator it = vec.begin();
for(;vec.size() != 0;){
it += k; //k is a const integer
if(it >= vec.end()){
it -= items.end(); // something like that, but this syntax is obviously invalid
}
vec1.push_back(*it);
it = vec.erase(it);
}
So perhaps I increment vec{1,2,3,4,5} by 3, it should first remove 3, then 1 and put them in vec1, in a sense it loops around by however was left when it reached the end. I tried a bunch of different syntaxes, but there's always a type mismatch error. Is there an elegant way of doing this?
XY-solution: I recommend keeping an index instead of an iterator and use the remainder operator.
for(std::size_t i = 0; vec.size() != 0;){
i = (i + k) % vec.size();
vec1.push_back(vec[i]);
vec.erase(vec.begin() + i);
}
So perhaps I increment vec{1,2,3,4,5} by 3, it should first remove 3
This doesn't match your attempted code. If you increment iterator to first element by 3 and erase it, then you'd be erasing the element with value 4.

How to erase multiple elements from std::vector<> by index using erase function?

I have a vector a storing values [0 1 2 3 5] and other vector removelist storing the indexes to be removed [0 1 2] in order to leave [3 5] at the end. When I'm implementing the following code, it would remove items unexpectedly since the vector a will be changing order during the process. Is there any way for me to achieve my target?
for (int i = 0; i<removelist.size() ; i++)
a.erase(a.begin() + removelist[i]);
Reverse the order you remove values, i.e. use the reverse iterators of removelist. This of course relies on removelist being sorted.
Perhaps something like
std::sort(removelist.begin(), removelist.end()); // Make sure the container is sorted
for (auto &i = removelist.rbegin(); i != removelist.rend(); ++ i)
{
a.erase(a.begin() + *i);
}
Not necessarily more efficient, but you can do this without sorting using remove_if:
auto& rm = removelist; // for brevity
a.erase(remove_if(begin(a), end(a), [&](int i) {
auto idx = distance(begin(v), find(begin(v), end(v), i));
return find(begin(rm), end(rm), idx) != end(rm);
}, end(a));
The solution to this is to copy the elements you want to keep to a new vector:
// pseudocode:
vector tmp;
tmp.reserve(a.size() - removelist.size());
for (i=0; i<a.size(); ++i) {
if (i not in removelist) {
tmp.push_back(a[i]);
}
}
a.swap(tmp);
Notes:
You have to make sure that the indices are unique, otherwise the preallocation fails.
This avoids various reallocations using the preallocated, temporary vector. The reallocations of a also avoid the index shift in your approach.
If the elements in removelst are sorted, this can be implemented a bit more efficiently.
I wonder where that list comes from. Can't you remove the elements on the fly instead of creating a temporary list?
Adapted from #Yam Marcovic's answer, not using find but use the exact address to find the index:
a.erase(std::remove_if(a.begin(), a.end(), [&](const int& i) {
auto idx = ((void*)&i - (void*)&*a.begin());
return std::find(removelist.begin(), removelist.end(), idx) != removelist.end();
}), a.end());

Going through and inserting data to std vectors

I never had this kind of problems when using QVector, but unfortunately I cannot use qt in this one. What I am trying to do is to go through 2 vectors which contains points X and Y as doubles.
I am trying to insert some x and y points in between the points that are predefined. The problem is that after second loop everything goes bad, iterators start to get weird values and the points are inserted in the 0 index all the time. This iterator thing is really confusing, I can't understand why I can't insert to my vectors simply using intiger indexes. I tried the C++11 way as well, but didn't give me anything.
The m_x vector represents time in seconds and m_y vector represents temperature. m_x is double, but it can only take integer positive values.
int CProcessData::calculateMidPoints()
{
if((0 == m_x.size()) || (0 == m_y.size()))
return 1;
std::vector<double>::iterator itX = m_x.begin();
std::vector<double>::iterator itY = m_y.begin();
double midPointY = 0;
for (uint32_t i = 0; i < (m_x.size() - 1); i++)
{
if (i != m_x[i])
{
m_x.insert(itX, (double)i);
midPointY = (m_y[i - 1] * 1 + m_y[i] * (1 / (m_x[i + 1] - m_x[i]))) /
(1 + (1 / (m_x[i + 1] - m_x[i])));
m_y.insert(itY, midPointY);
}
else
{
itX++;
itY++;
}
}
return 0;
}
calling insert() is going to invalidate your iterators.
From http://en.cppreference.com/w/cpp/container/vector/insert:
Causes reallocation if the new size() is greater than the old capacity(). If the new size() is greater than capacity(), all iterators and references are invalidated. Otherwise, only the iterators and references before the insertion point remain valid. The past-the-end iterator is also invalidated.
The insert() function returns a iterator to the inserted element. Since this is a valid iterator you can capture it and use it for the next iteration. Here is a little example of how that works:
std::vector<int> data = { 1, 2, 3, 4, 5 };
auto it = data.begin();
std::advance(it, 3);
for (int i = 0; i < 3; i++)
{
it = data.insert(it, 9);
++it;
}
data will contain { 1, 2, 3, 9, 9, 9, 4, 5 } at the end of the for loop.

Why can't I delete last element of vector

I have stl vector consisting of several elements. I need to iterate through this vector and delete elements which meets some criteria. So I wrote this code
for (int j = imageDataVector.size()-1; j >= 0; j--) {
if(imageDataVector[i] < threshold)
imageDataVector.erase(imageDataVector.end() - j);
}
This code works fine for almost all cases, however if all elements of vector meets the criteria I get an error:
vector erase iterator outside the range
This error occurs if I have only one element left in the vector. What do I do wrong ?
if(imageDataVector[i] < threshold)
imageDataVector.erase(imageDataVector.end()-j);
Should likely be:
if(imageDataVector[j] < threshold)
imageDataVector.erase(imageDataVector.begin()+j);
EDIT: for completeness, the erase-remove way and the iterator way:
imageDataVector.erase(std::remove_if(imageDataVector.begin(), imageDataVector.end(), std::bind2nd(std::less<vector_data_type>(), threshold)), imageDataVector.end());
vector<type>::iterator it = imageDataVector.begin();
while (it != imageDataVector.end()) {
if (*it < threshold)
it = imageDataVector.erase(it);
else
++it;
}
You're mixing forward and backward indexing.
I'd consider using std::remove_if instead. That way if you're removing multiple elements you don't shift the entire vector forwards on each erase.
It would look something like this:
imageDataVector.erase(std::remove_if(imageDataVector.begin(), imageDataVector.end(), std::bind2nd(std::less<data_type>(), threshold)), imageDataVector.end());
Alternately try the following, noting that it will result in a lot of movement if you remove multiple items from the vector.
for (int j=imageDataVector.size()-1 ;j>=0;j--)
{
if(imageDataVector[i] < threshold)
imageDataVector.erase(imageDataVector.begin()+j);
}
You're trying to count down j to zero, and imageDataVector.end() - 0 is not a valid iterator. In the standard C++ library containers, the end iterator points one past the last element, not at the last element.

std::remove not working correctly, still has extra elements

std::vector<int> v = {1,2,3,4,5};
auto i = std::remove(v.begin(),v.end(),3);
for(auto j = v.begin(); j!= v.end();++j)
std::cout << *j;
Actual output : 12455
Where does extra 5 come from?
Desired output : 1245
How to achieve this?
remove doesnt actually remove the elements
Remove removes from the range [first, last) all elements that are equal to value. That is, remove returns an iterator new_last such that the range [first, new_last) contains no elements equal to value. 1 The iterators in the range [new_last, last) are all still dereferenceable, but the elements that they point to are unspecified. Remove is stable, meaning that the relative order of elements that are not equal to value is unchanged.`
std::remove algorithm works only using a pair of forward iterators and in general knows nothing about the underlying container.
You need to use the erase-remove idiom to actually remove the element i.e combine erase with remove
auto i = std::remove(v.begin(),v.end(),3);
v.erase(i,v.end());
for(auto j = v.begin(); j!= v.end();++j)
std::cout << *j;
Read the documentation for std::remove again.
The function does not remove elements from a container (in fact, it doesn't even know that a container is involved, as it only sees iterators), it merely moves values in a sequence and return a new iterator i such that all the interval [ begin .. i [ contains all non-removed elements in the original order. Elements left over in [ i .. end [ are unspecified, and it is your responsibility to eliminate that interval from a container (if you need it):
auto i = std::remove(...);
v.erase(i,v.end());
The reason why you have an additional 5 is that the typical removal algorithm copies values into holes left by removed values, and since values past the i iterator are never overwritten, they remain the same as in the original sequence. This behavior, however, is not reliable - just eliminate the values past i without reading them.
remove returns the new end. So the fix of your code is this:
std::vector<int> v = {1,2,3,4,5};
auto newEnd = std::remove(v.begin(),v.end(),3);//return value stored in newEnd
for(auto j = v.begin(); j!= newEnd ;++j) //note j!=newEnd
std::cout << *j;
Output:
1245
Check it out yourself : http://www.ideone.com/3AMD9
Actually, std::remove moves at beginning of the range all elements which don't satisfy predicate, AND returns iterator of the new end.
So usage is erase-remove idiom:
v.erase(std::remove(v.begin(), v.end(), 3), v.end());
In c++20, it can be simplified thanks to std::erase:
std::erase(v, 3);
It's seams that you are printing the n+1 position of the vector in the for() statement.
It should be:
for(auto j = v.begin(); j!= v.end();j++)
std::cout << *j;
j++ no ++j