Erasing using iterator from 'find' or 'remove' - c++

I would like to know what's the best practice to remove an element from a vector in C++.
I have seen many times people using std::remove to find and delete the element, and then using erase to remove the element from the vector.
But why is it better than using find to get the iterator of the element you want to remove and then using the erase with that iterator?
Thanks

std::find followed by vector::erase will erase the first occurrence of an object with the given value from the vector.
std::vector<int> vec{1,3,3,8,3,5};
vec.erase(std::find(vec.begin(), vec.end(), 3));
//vec == {1,3,8,3,5}
std::remove followed by vector::erase will remove every occurrence of an object with the given value from the vector.
std::vector<int> vec{1,3,3,8,3,5};
vec.erase(std::remove(vec.begin(), vec.end(), 3), vec.end());
//vec == {1,8,5}
Neither is better, they just do different things.
std::remove is more generally useful, and that is why it is more often seen; in particular, std::remove followed by vector::erase does nothing when the element is not present in the vector, while std::find followed by vector::erase has undefined behavior.
Note that both "find-erase", "remove-erase" maintain the relative order of the elements. If you want to remove an element from the vector but do not care about the resulting order of the elements, you can use "find-move-pop_back" or "partition-erase":
//find-move-pop_back
std::vector<int> vec{1,3,3,8,3,5};
*std::find(vec.begin(), vec.end(), 3) = std::move(vec.back());
vec.pop_back();
//partition-erase
std::vector<int> vec{1,3,3,8,3,5};
vec.erase(
std::partition(vec.begin(), vec.end(), [](int v){return v != 3;}),
vec.end());

Related

remove algorithm unexpected behaviour [duplicate]

I am bit confused about the difference between the usage of std::remove algorithm. Specifically I am not able to understand what is being removed when I use this algorithm. I wrote a small test code like this:
std::vector<int> a;
a.push_back(1);
a.push_back(2);
std::remove(a.begin(), a.end(), 1);
int s = a.size();
std::vector<int>::iterator iter = a.begin();
std::vector<int>::iterator endIter = a.end();
std::cout<<"Using iter...\n";
for(; iter != endIter; ++iter)
{
std::cout<<*iter<<"\n";
}
std::cout<<"Using size...\n";
for(int i = 0; i < a.size(); ++i)
{
std::cout<<a[i]<<"\n";
}
The output was 2,2 in both the cases.
However, if I use erase with the remove something like this:
a.erase(std::remove(a.begin(), a.end(), 1), a.end());
I get the output as 2.
So my questions are:
(1). Is there any use of std::remove other than using it with erase function.
(2). Even after doing std::remove, why a.size() returns 2 and not 1?
I read the item in Scott Meyer's Effective STL book about the erase-remove idiom. But am still having this confusion.
remove() doesn't actually delete elements from the container -- it only shunts non-deleted elements forwards on top of deleted elements. The key is to realise that remove() is designed to work on not just a container but on any arbitrary forward iterator pair: that means it can't actually delete the elements, because an arbitrary iterator pair doesn't necessarily have the ability to delete elements.
For example, pointers to the beginning and end of a regular C array are forward iterators and as such can be used with remove():
int foo[100];
...
remove(foo, foo + 100, 42); // Remove all elements equal to 42
Here it's obvious that remove() cannot resize the array!
What does std::remove do?
Here's pseudo code of std::remove. Take few seconds to see what it's doing and then read the explanation.
Iter remove(Iter start, Iter end, T val) {
Iter destination = start;
//loop through entire list
while(start != end) {
//skip element(s) to be removed
if (*start == val) {
start++;
}
else //retain rest of the elements
*destination++ = *start++;
}
//return the new end of the list
return destination;
}
Notice that remove simply moved up the elements in the sequence, overwriting the values that you wanted to remove. So the values you wanted to remove are indeed gone, but then what's the problem? Let say you had vector with values {1, 2, 3, 4, 5}. After you call remove for val = 3, the vector now has {1, 2, 4, 5, 5}. That is, 4 and 5 got moved up so that 3 is gone from the vector but the size of vector hasn't changed. Also, the end of the vector now contains additional left over copy of 5.
What does vector::erase do?
std::erase takes start and end of the range you want to get rid off. It does not take the value you want to remove, only start and end of the range. Here's pseudo code for how it works:
erase(Iter first, Iter last)
{
//copy remaining elements from last
while (last != end())
*first++ = *last++;
//truncate vector
resize(first - begin());
}
So the erase operation actually changes the size of container and frees up the memory.
The remove-erase idiom
The combination of std::remove and std::erase allows you to remove matching elements from the container so that container would actually get truncated if elements were removed. Here's how to do it:
//first do the remove
auto removed = std::remove(vec.begin(), vec.end(), val);
//now truncate the vector
vec.erase(removed, vec.end());
This is known as the remove-erase idiom. Why is it designed like this? The insight is that the operation of finding elements is more generic and independent of underlying container (only dependent on iterators). However operation of erase depends on how container is storing memory (for example, you might have linked list instead of dynamic array). So STL expects containers to do its own erase while providing generic "remove" operation so all containers don't have to implement that code. In my view, the name is very misleading and std::remove should have been called std::find_move.
Note: Above code is strictly pseudocode. The actual STL implementation is more smarter, for example, using std::move instead of copy.
std::remove does not remove the actual objects, rather, pushes them to the end of the container. Actual deletion and deallocation of memory is done via erase. So:
(1). Is there any use of std::remove other than using it with erase function.
Yes, it helps to get a pair of iterators to a new sequence without having worry about proper de-allocation etc.
(2). Even after doing std::remove, why a.size() returns 2 and not 1?
The container still holds to those objects, you only have a new set of iterators to work with. Hence the size is still what it used to be.
i faced the same issue, trying to understand the difference.
the explanations that have been give so far are right on the money, but i only understood them after seeing an example;
#include <algorithm>
#include <string>
#include <iostream>
#include <cctype>
int main()
{
std::string str1 = "Text with some spaces";
std::string::iterator it = remove(str1.begin(), str1.end(), 't');
std::cout << str1 << std::endl;// prints "Tex wih some spaceses"
for (str1.begin();it != str1.end(); ++it)
{
std::cout << *it; //prints "es"
}
}
as you can see, the remove, only moves the lower case 't' to the end of the string, while returning a new iterator to the end of the new string (new string is the old string up to where the removed element are inserted)
this is why when you print the iterator that you got from "remove"
"Text with some spaces"
^ ^removes both 't', then shift all elements forward -1 //what we want to remove
"Text with some spaces"
^ end of string -2 //original state of string
"Tex with some spacess"
^end of string -3 //first 't' removed
"Tex wih some spaceses"
^end of string -4 //second 't' removed
"Tex wih some spaceses"
^new iterator that remove() returned -5 // the state of string after "remove" and without "erase"
if you pass the iterator you obtained from step 5 to "erase()" it will know to erase from there to the end of string re-sizing the string in process
To remove element with some condition(equal some value or other condition like less than) in container like vector, it always combine function member function erase and std::remove or std::remove_if.
In vector, the function erase can just delete element by position, like:
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
But if you want to erase elements with some condition, you can combine it with std::remove or std::remove_if.
For example, you want to erase all the elements 6 in the below vector:
std::vector<int> vec{6, 8, 10, 3, 4, 5, 6, 6, 6, 7, 8};
// std::remove move elements and return iterator for vector erase funtion
auto last = std::remove(vec.begin(), vec.end(), 6);
for(int a:vec)
cout<<a<<" ";
cout<<endl;
// 8 10 3 4 5 7 8 6 6 7 8
vec.erase(last, vec.end());
for(int a:vec)
cout<<a<<" ";
cout<<endl;
// 8 10 3 4 5 7 8
std::remove works as below, it does't erase any elements, it just move elements and returns the iterator.
Possible implementation:
template< class ForwardIt, class T >
ForwardIt remove(ForwardIt first, ForwardIt last, const T& value)
{
first = std::find(first, last, value);
if (first != last)
for(ForwardIt i = first; ++i != last; )
if (!(*i == value))
*first++ = std::move(*i);
return first;
}
Conclusion:
If you want to remove elements with some condition, you use vector::iterator erase (iterator first, iterator last); essentially.
First get range start:
auto last = std::remove(vec.begin(), vec.end(), equal_condition_value);
erase by range(always with end())
vec.erase(last, vec.end());
cited:
https://en.cppreference.com/w/cpp/algorithm/remove
Simplest I can come up with:
erase() is something you can do to an element in a container. Given an iterator/index into a container, erase( it ) removes the thing the iterator refers to from the container.
remove() is something you can do to a range, it re-arranges that range but doesn't
erase anything from the range.
remove doesn't "really" remove
anything, because it can't.
In order to "actually" remove the elements from container you need to access container APIs. Where as remove works only with iterators irrespective of what containers those iterators points to. Hence, even if remove wants an "actual remove", it can't.
Remove overwrite "removed" elements by the following elements that were not removed and then it is up to the caller to decide to use the returned new logical end instead of the original end.
In your case remove logically removed 1 from vector a but size remained to 2 itself. Erase actually deleted the elements from vector. [ from vector new end to old end ]
The main idea of remove is it cannot change the number of elements and it just remove elements from a range as per criteria.

Delete elements in a vector using a list of iterators?

Lets suppose I have a unsorted list of iterators to delete defined as
std::vector<std::vector<int>::iterator> _unsortedIterList;
Of another vector defined as:
std::vector<int> _listValues;
Then this code will not work (since erasing will invalidate the remaining iterators).
for ( auto it: _unsortedIterList)
{
_listValues.erase(it);
}
Is there a way to erase all of the iterators properly?
Erasing invalidates iterators at or after the point of erase. So all you have to do is ensure that we erase back to front:
// because random access iterators are comparable
std::sort(_unsortedIterList.begin(), _unsortedIterList.end(),
std::greater<>{});
// now this is back-to-front, so each erase will keep every other iterator valid
for (auto it : _unsortedIterList) {
_listValues.erase(it);
}

Remove items from two vectors depending on the values inside one vector

I have two integer vectors of equal length. Let's say I want to remove all items in the first vector which are NAN. Obviously, I use the remove_if algorithm. Let's say this removes elements that were at indexes 1,2,5. I then want to remove items from the second vector at these indexes.
What's the most canonical C++ way of doing this?
This can be done using Boost by creating a zip_iterator and then iterating over the tuple of iterators from both containers in parallel.
First pass a pair of zip_iterators to std::remove_if, and have the predicate inspect the elements of the first vector for NaN
auto result = std::remove_if(boost::make_zip_iterator(boost::make_tuple(v1.begin(), v2.begin())),
boost::make_zip_iterator(boost::make_tuple(v1.end(), v2.end())),
[](boost::tuple<double, int> const& elem) {
return std::isnan(boost::get<0>(elem));
});
Then use vector::erase to remove the unneeded elements.
v1.erase(boost::get<0>(result.get_iterator_tuple()), v1.end());
v2.erase(boost::get<1>(result.get_iterator_tuple()), v2.end());
Live demo
The boilerplate required to create the zipped iterator ranges can be further reduced by using boost::combine and Boost.Range's version of remove_if.
auto result = boost::remove_if(boost::combine(v1, v2),
[](boost::tuple<double, int> const& elem) {
return std::isnan(boost::get<0>(elem));
});
Live demo
Use a vector<pair<int, int>> to tie the two vector together. Then, perform your remove based on the first element to and get rid of both at the same time.

std::remove_if not working properly [duplicate]

This question already has answers here:
Erasing elements from a vector
(6 answers)
Closed 8 years ago.
Here my code. I want remove from vector all elements with successfully called method 'release'.
bool foo::release()
{
return true;
}
// ...
vector<foo> vec;
// ...
remove_if(vec.begin(), vec.end(), [](foo & f) { return f.release() == true; });
// ...
But remove_if not deleting all elements from vector vec. How remove_if works?
std::remove_if re-arranges the elements of the vector such that the elements you want to keep are in the range [vec.begin(), return_iterator) (note the partially open range). So you need to call std::vector::erase to make sure the vector contains only the desired elements. This is called the erase-remove idiom:
auto it = remove_if(vec.begin(),
vec.end(),
[](foo & f) { return f.release() == true; });
vec.erase(it, vec.end());
Here, I have split it into two lines for clarity, but it is often seen as a one-liner.
std::remove and std::remove_if do not actually remove anything but just give you an iterator by which you can then erase elements using the appropriate member function of whatever container you use. In std::vector's case, erase.
I invite you to read this old article from Scott Meyers: "My Most Important C++ Aha! Moments...Ever":
It was thus with considerable shock and a feeling of betrayal that I discovered that applying remove to a container never changes the number of elements in the container, not even if you ask it to remove everything. Fraud! Deceit! False advertising!
Because the remove_if algorithm operates on a range of elements denoted by two forward iterators, it has no knowledge of the underlying container or collection.
Thus, no elements are actually removed from the container. Rather, all elements which don't fit the remove criteria are brought together to the front of the range, in the same relative order.
The remaining elements are left in a valid, but unspecified, state. When this is done, remove returns an iterator pointing one element past the last unremoved element.
To actually eliminate elements from the container, remove should be combined with the container's erase member function (hence the name "erase-remove idiom").
How to use remove-erase idiom for removing empty vectors in a vector?
Erasing elements from a vector
See http://en.wikipedia.org/wiki/Erase-remove_idiom
std::remove_if doesn't actually obliterate erase the elements. What it does is move the elements that satisfies the criteria into the end of the range. It then returns an iterator to the first element of the removed (which are actually just moved) elements. It is on you then to erase that range from the container.
vector<foo> vec;
auto remove_start = remove_if(vec.begin(), vec.end(), [](foo & f) { return f.release() == true; });
vec.erase(remove_start, vec.end());
or
vec.erase(remove_if(vec.begin(), vec.end(),
[](foo & f) { return f.release() == true; }),
vec.end());

Erase-remove idiom: what happens when remove return past-the-end-iterator?

I got this question when I was reading erase-remove idiom (item 32) from Scott Meyers "Effective STL” book.
vector<int> v;
...
v.erase(remove(v.begin(), v.end(), 99), v.end());
remove basically returns the "new logical end” and elements of the original range that start at the "new logical end" of the range and continue until the real end of the range are the elements to be erased from container.
Sounds good. Now, let me ask my question:
In the above example, remove can return v.end() if 99 is not found in the vector v. It is basically passing past-the-end-iterator to erase method.
What happens when past-the-end-iterator is passed to the erase method? Does standard says it a UB?
If it is undefined behavior, then erase-remove idiom example in Scott Meyer’s book should have looked like:
vector<int> v;
...
vector<int>::iterator newEndIter = remove(v.begin(), v.end(), 99);
if(newEndIter != v.end() )
{
v.erase(newEndIter, v.end();
}
Any ideas on this?
I would think v.erase(v.end(), v.end()) would be well defined and erase nothing.
The C++ standard says that the erase(q1,q2) member "erases the elements in the range [q1,q2)" (cf. section 23.1.1). Since the range excludes the last element,
v.erase(v.end(), v.end());
is valid and erases nothing.
C++ Reference claims:
The iterator first does not need to be dereferenceable if first==last: erasing an empty range is a no-op.