Erasing an equal_range iterator - c++

I've got a pair of iterators:
pair <multimap<CFile,Filetype>::iterator, multimap<CFile,Filetype>::iterator> range;
range = m_DirectoryMap.equal_range(obj);
That pair holds duplicated elements in a MultiMap - e.g. there is 1 object that has 2 more duplicates (so basically 3 objects) and I need to remove 2 of them, so only 1 is left.
I was doing this by a simple while loop, like this:
auto it = range.first;
++it;
while (it != range.second)
it = m_DirectoryMap.erase(it);
After that, only 1 object was left - which is my goal.
Later I have found that I should probably try and erase the whole pair by 1 function call and there shouldn't be any needs for loops, like this:
m_DirectoryMap.erase(range.first, range.second);
This seems more cleaner, but the problem is that it removes all objects.
Then I tried:
m_DirectoryMap.erase(++range.first, range.second);
This seems to leave the first object and remove the rest, so it is working for me, but my question is - is this the right way to do, what I'm looking for?

Related

For loop exit condition with map iterator

I have a std::map<str,int> my_map
Right now, the key-value mapping looks like this -
{["apple",3],["addition",2],["app",7],["adapt",8]}
Objective:
Calculate the sum of values of keys with a given prefix.
Example : sum("ap") should return 10 (3 + 7).
I could implement it with two loops and an if condition. But, I'm trying to understand the following code that's submitted by someone to implement this.
for (auto it = my_map.lower_bound(prefix);
it != my_map.end() && it->first.substr(0, n) == prefix;
it++)
Won't the loop condition become false in the middle of iterating through my_map hence calculating an incorrect sum ?
I don't know how the code is able to give the right result. Why wouldn't the loop exit when it gets to key "addition" while looking for prefix "ap" ?
Any kind of help is appreciated.
The loop is completely correct, but not so readable at first sight.
We have std::map which is an associative container and sorted according to the compare function provided. For your map (i.e std::map<std:.string, int>), it will be sorted according to the std::string (i.e key).
So your map is already ordered like :
{["adapt",8], ["addition",2], ....., ["app",7], ["apple",3], .... }
Now let's start with the std::lower_bound:
Returns an iterator pointing to the first element in the range [first,
Last) that is not less than (i.e. greater or equal to) value, or last
if no such element is found.
Meaning at the loop start:
auto it = my_map.lower_bound(prefix);
iterator it is pointing to the map entry ["app",7]. In otherwards the iteration starts from the first possible start.
["app",7], ["apple",3], ....
Now the condition comes in to play:
it != my_map.end() && it->first.substr(0, n) == prefix;
The first one to see whether the iterator is valid (i.e. it != my_map.end()).
The second one checks whether the prefix is the same as the key start (i.e. it->first.substr(0, n) == prefix;). Since we start from the sorted possible prefix start, the outcome of the loop will be correct.

How to avoid out of range exception when erasing vector in a loop?

My apologies for the lengthy explanation.
I am working on a C++ application that loads two files into two 2D string vectors, rearranges those vectors, builds another 2D string vector, and outputs it all in a report. The first element of the two vectors is a code that identifies the owner of the item and the item in the vector. I pass the owner's identification to the program on start and loop through the two vectors in a nested while loop to find those that have matching first elements. When I do, I build a third vector with components of the first two, and I then need to capture any that don't match.
I was using the syntax "vector.erase(vector.begin() + i)" to remove elements from the two original arrays when they matched. When the loop completed, I had my new third vector, and I was left with two vectors that only had elements, which didn't match and that is what I needed. This was working fine as I tried the various owners in the files (the program accepts one owner at a time). Then I tried one that generated an out of range error.
I could not figure out how to do the erase inside of the loop without throwing the error (it didn't seem that swap and pop or erase-remove were feasible solutions). I solved my problem for the program with two extra nested while loops after building my third vector in this one.
I'd like to know how to make the erase method work here (as it seems a simpler solution) or at least how to check for my out of range error (and avoid it). There were a lot of "rows" for this particular owner; so debugging was tedious. Before giving up and going on to the nested while solution, I determined that the second erase was throwing the error. How can I make this work, or are my nested whiles after the fact, the best I can do? Here is the code:
i = 0;
while (i < AIvector.size())
{
CHECK:
j = 0;
while (j < TRvector.size())
{
if (AIvector[i][0] == TRvector[j][0])
{
linevector.clear();
// Add the necessary data from both vectors to Combo_outputvector
for (x = 0; x < AIvector[i].size(); x++)
{
linevector.push_back(AIvector[i][x]); // add AI info
}
for (x = 3; x < TRvector[j].size(); x++) // Don't need the the first three elements; so start with x=3.
{
linevector.push_back(TRvector[j][x]); // add TR info
}
Combo_outputvector.push_back(linevector); // build the combo vector
// then erase these two current rows/elements from their respective vectors, this revises the AI and TR vectors
AIvector.erase(AIvector.begin() + i);
TRvector.erase(TRvector.begin() + j);
goto CHECK; // jump from here because the erase will have changed the two increments
}
j++;
}
i++;
}
As already discussed, your goto jumps to the wrong position. Simply moving it out of the first while loop should solve your problems. But can we do better?
Erasing from a vector can be done cleanly with std::remove and std::erase for cheap-to-move objects, which vector and string both are. After some thought, however, I believe this isn't the best solution for you because you need a function that does more than just check if a certain row exists in both containers and that is not easily expressed with the erase-remove idiom.
Retaining the current structure, then, we can use iterators for the loop condition. We have a lot to gain from this, because std::vector::erase returns an iterator to the next valid element after the erased one. Not to mention that it takes an iterator anyway. Conditionally erasing elements in a vector becomes as simple as
auto it = vec.begin()
while (it != vec.end()) {
if (...)
it = vec.erase(it);
else
++it;
}
Because we assign erase's return value to it we don't have to worry about iterator invalidation. If we erase the last element, it returns vec.end() so that doesn't need special handling.
Your second loop can be removed altogether. The C++ standard defines functions for searching inside STL containers. std::find_if searches for a value in a container that satisfies a condition and returns an iterator to it, or end() if it doesn't exist. You haven't declared your types anywhere so I'm just going to assume the rows are std::vector<std::string>>.
using row_t = std::vector<std::string>;
auto AI_it = AIVector.begin();
while (AI_it != AIVector.end()) {
// Find a row in TRVector with the same first element as *AI_it
auto TR_it = std::find_if (TRVector.begin(), TRVector.end(), [&AI_it](const row_t& row) {
return row[0] == (*AI_it)[0];
});
// If a matching row was found
if (TR_it != TRVector.end()) {
// Copy the line from AIVector
auto linevector = *AI_it;
// Do NOT do this if you don't guarantee size > 3
assert(TR_it->size() >= 3);
std::copy(TR_it->begin() + 3, TR_it->end(),
std::back_inserter(linevector));
Combo_outputvector.emplace_back(std::move(linevector));
AI_it = AIVector.erase(AI_it);
TRVector.erase(TR_it);
}
else
++AI_it;
}
As you can see, switching to iterators completely sidesteps your initial problem of figuring out how not to access invalid indices. If you don't understand the syntax of the arguments for find_if search for the term lambda. It is beyond the scope if this answer to explain what they are.
A few notable changes:
linevector is now encapsulated properly. There is no reason for it to be declared outside this scope and reused.
linevector simply copies the desired row from AIVector rather than push_back every element in it, as long as Combo_outputvector (and therefore linevector) contains the same type than AIVector and TRVector.
std::copy is used instead of a for loop. Apart from being slightly shorter, it is also more generic, meaning you could change your container type to anything that supports random access iterators and inserting at the back, and the copy would still work.
linevector is moved into Combo_outputvector. This can be a huge performance optimization if your vectors are large!
It is possible that you used an non-encapsulated linevector because you wanted to keep a copy of the last inserted row outside of the loop. That would prohibit moving it, however. For this reason it is faster and more descriptive to do it as I showed above and then simply do the following after the loop.
auto linevector = Combo_outputvector.back();

How to find the second to last element in a vector in C++?

I am trying to build a program that uses the second to last element in a vector, so far I've used:
(arr2.rbegin()+1)
If I use a comparison operator in a conditional such as:
if(arr2.rbegin()+1 == true)
I get an error message: no match for operator ==
Many of the answers and comments have the right idea but really ugly syntax. Here are two nice ways to express that.
arr2.end()[-2] // end() is past the last element, -1 for last element, -2 for second-last
arr2.rbegin()[1] // rbegin() is reverse order starting at 0 for last element, 1 for second-last
Demo: http://ideone.com/2cZeUq
It works because RandomAccessIterator, which vector has, is required to provide operator[] such that it[n] is equivalent to *(it + n), just like for pointers.
So the code in your question becomes just
if (arr2.rbegin()[1]) // test penultimate element
looking at the documentation here
http://www.cplusplus.com/reference/vector/vector/?kw=vector
I'd expect you to access your element by
secondToLast = myVector[myVector.size() - 2];
You can try doing like this:-
if(*(arr2.rbegin()+1))
Sometimes there might be less than 2 items in the list, so myVector.size() - 2 or other direct accessors will throw an error. I've done the following . . .
if (myVector.size() > 1)
{
secondToLast = myVector[myVector.size() - 2];
}
It depends on what you mean by "second to last element". Take the following iterator definition...
vector<int>::iterator it = arr2.end();
it--;
it--;
You have to decriment the iterator twice because when you declare the iterator to "point" to the end, it actually references the location AFTER the last element in the vector.
Dont forget that when you want the value that the iterator points to, you have to dereference it. like so...
cout << *it;
Mostly for lulz, but if your elements are non-scalar and you need to access a member of the element in question, you can use the ++-> construction:
std::vector<std::pair<int, int>> arr = ...;
auto grug = arr.rbegin()[1].first;
auto leet = arr.rbegin()++->first;
assert(grug == leet);
The way it works is we post-increment the iterator returned by rbegin() with ++ and then access it with ->. It is actually superior to the clearest [1] form in the sense it will work on any iterator, not only random access iterator.
Post it on review and get some popcorn.
There are many ways you can access elements from the back
one you can use is the back property that comes with std::vector container
and if you want to access an element from the back (either last element or up to n)
you can do this
std::vector vec{1,2,3};
int lastsecond = vec.back()-1; will give you -> 2;
you can check vector properties which there is a decent bit.
https://en.cppreference.com/w/cpp/container/vector

Weird behaviour with vector::erase and std::remove_if with end range different from vector.end()

I need to remove elements from the middle of a std::vector.
So I tried:
struct IsEven {
bool operator()(int ele)
{
return ele % 2 == 0;
}
};
int elements[] = {1, 2, 3, 4, 5, 6};
std::vector<int> ints(elements, elements+6);
std::vector<int>::iterator it = std::remove_if(ints.begin() + 2, ints.begin() + 4, IsEven());
ints.erase(it, ints.end());
After this I would expect that the ints vector have: [1, 2, 3, 5, 6].
In the debugger of Visual studio 2008, after the std::remove_if line, the elements of ints are modified, I'm guessing I'm into some sort of undefined behaviour here.
So, how do I remove elements from a Range of a vector?
Edit: Sorry, the original version of this was incorrect. Fixed.
Here's what's going on. Your input to remove_if is:
1 2 3 4 5 6
^ ^
begin end
And the remove_if algorithm looks at all numbers between begin and end (including begin, but excluding end), and removes all elements between that match your predicate. So after remove_if runs, your vector looks like this
1 2 3 ? 5 6
^ ^
begin new_end
Where ? is a value that I don't think is deterministic, although if it's guaranteed to be anything it would be 4. And new_end, which points to the new end of the input sequence you gave it, with the matching elements now removed, is what is returned by std::remove_if. Note that std::remove_if doesn't touch anything beyond the subsequence that you gave it. This might make more sense with a more extended example.
Say that this is your input:
1 2 3 4 5 6 7 8 9 10
^ ^
begin end
After std::remove_if, you get:
1 2 3 5 7 ? ? 8 9 10
^ ^
begin new_end
Think about this for a moment. What it has done is remove the 4 and the 6 from the subsequence, and then shift everything within the subsequence down to fill in the removed elements, and then moved the end iterator to the new end of the same subsequence. The goal is to satisfy the requirement that the (begin, new_end] sequence that it produces is the same as the (begin, end] subsequence that you passed in, but with certain elements removed. Anything at or beyond the end that you passed in is left untouched.
What you want to get rid of, then, is everything between the end iterator that was returned, and the original end iterator that you gave it. These are the ? "garbage" values. So your erase call should actually be:
ints.erase(it, ints.begin()+4);
The call to erase that you have just erases everything beyond the end of the subsequence that you performed the removal on, which isn't what you want here.
What makes this complicated is that the remove_if algorithm doesn't actually call erase() on the vector, or change the size of the vector at any point. It just shifts elements around and leaves some "garbage" elements after the end of the subsequence that you asked it to process. This seems silly, but the whole reason that the STL does it this way is to avoid the problem with invalidated iterators that doublep brought up (and to be able to run on things that aren't STL containers, like raw arrays).
Erasing elements in std::vector invalidates iterators past the removed element, so you cannot use "foreign" functions that accept ranges. You need to do that in a different way.
EDIT:
In general, you can use the fact that erasing one element "shifts" all elements at further positions one back. Something like this:
for (size_t scan = 2, end = 4; scan != end; )
{
if (/* some predicate on ints[scan] */)
{
ints.erase (ints.begin () + scan);
--end;
}
else
++scan;
}
Note that std::vector isn't suited for erasing elements in the middle. You should consider something else (std::list?) if you do that often.
EDIT 2:
As clarified by comments, first paragraph is not true. In such case std::remove_if should be more efficient than what I suggested in the first edit, so disregard this answer. (Keeping it for the comments.)
The behavior isn't weird - you're erasing the wrong range. std::remove_if moves elements it "removes" to the end of the input range. In this case, what you're looking for would be to do:
ints.erase(it, ints.begin() + 4 /* your end of range */);
From C++ in a Nutshell:
The remove_if function template
"removes" items for which pred returns
false from the range [first, last).
The return value is one past the new
end of the range. The relative order
of items that are not removed is
stable.
Nothing is actually erased from the
underlying container; instead, items
to the right are assigned to new
positions so they overwrite the
elements for which pred returns false.
See Figure 13-13 (under remove_copy)
for an example of the removal process.

How can I eliminate an element in a vector if a condition is met

I have a vector of Rect: vector<Rect> myRecVec;
I would like to remove the ones which are overlapping in the vector:
So I have 2 nested loop like this:
vector<Rect>::iterator iter1 = myRecVec.begin();
vector<Rect>::iterator iter2 = myRecVec.begin();
while( iter1 != myRecVec.end() ) {
Rectangle r1 = *iter1;
while( iter2 != myRecVec.end() ) {
Rectangle r2 = *iter1;
if (r1 != r2) {
if (r1.intersects(r2)) {
// remove r2 from myRectVec
}
}
}
}
My question is how can I remove r2 from the myRectVect without screwing up both my iterators? Since I am iterating a vector and modifying the vector at the same time?
I have thought about putting r2 in a temp rectVect and then remove them from the rectVect later (after the iteration). But how can I skip the ones in this temp rectVect during iteration?
You need to increment iter1 and iter2.
erase returns an iterator to the next element. When you delete, use this instead of incrementing the iterator.
Like so:
while( iter1 != myRecVec.end() ) {
Rectangle r1 = *iter1;
iter2 = iter1 + 1;
while( iter2 != myRecVec.end() ) {
Rectangle r2 = *iter2;
if( r1.intersects(r2) ) {
iter2 = myRectVec.erase(iter2);
}
else {
++iter2;
}
++iter1;
}
First of all, you probably only want to run iter2 from iter1 to end, since r1.intersects(r2) iff r2.intersects(r1). Once you make this adjustment, all you will have to do is not increment r2 in the same cycle you erase it. You can do this, because erasing an element guaranteed does not cause std::vector to reallocate memory.
Another technique is to start at the end and iterate to the front, then the current iterator is alwasy valid and the the begin() of course never changes.
It's also more efficient since you move fewer elements.
If you are removing a lot of items, it may be faster (and easier) to copy those that are NOT eliminated to a new vector and then replace the original.
Finally if the elements can be sorted you can use set_intersects to find the intersecting vector ranges.
Use vector::erase which returns an interator to the element after the erased element. You can use this iterator to keep looping, (after checking it against .end()) . Don't use any other iterators since it may have been invalidated.
Using erase is probably a bad idea because it causes all of the rectangles appearing after the erasure point to be copied one entry down. Although you are spending O(n^2) time anyway just on the comparisons, the added copying might amount to significant overhead. I would strongly suggest copying the recatngles you want to keep into an output vector instead. (If you want the operation to affect the input vector, start by cloning it and clearing it, and then filter the clone and copy the surviving rectangles back to the original vector.
Did you consider the fact that in general there are many ways to remove rectangles such that the remaining ones do not overlap? Do you have any preference?
Assuming the answer to 2 is that any maximal subset will do (maximal subset = a subset X such that each rectangle not in X intersects at least one that is in X), if you first sort the rectangles by, say, their bottom left x coordinate (and then possibly secondarily by the y coordinate) you stand a good chance of reducing the total number of comaprisons, although the worst case remains O(n^2).
Does the order of elements in the vector matter? If not, swap the item you want to delete with myRecVec.back() (may no-op but that's ok) and then do a pop_back. This shouldn't invalidate iterators and will probably perform better than shifting the elements forward on each erase.
If order does matter you can use reverse iteration instead of forward iteration (you would save an iterator of the item to erase and then increment the reverse iterator).