I would like to iterate and erase items from std::vector, but I wish to compare each item to previous and next items in the vector and to perform some calculations. If a certain condition is met, item will be erased. Is it possible to pass more than one argument, iterators for previous, current and next vector items, to the function remove-if receives? if not, how can I access previous and next iterator items inside the function passed to remove-if?
Implementation:
template<class _FwdIt, class _Pr>
inline _FwdIt remove_if_with_prev_and_next(_FwdIt _First, _FwdIt _Last, _Pr _Pred)
{
if (_First == _Last) {
return _First;
}
auto first = _First;
auto next = std::next(first);
std::vector<char> remove; // vector<bool> is bad:-(
remove.push_back(_Pred(nullptr, *first, next == _Last ? nullptr : &*next) ? 1 : 0);
while (next != _Last) {
auto prev = first;
first = next;
++next;
remove.push_back(_Pred(&*prev, *first, next == _Last ? nullptr : &*next) ? 1 : 0);
}
auto remove_it = remove.begin();
// assume remove_if calls the predicate only once for every element, in order
return std::remove_if(_First, _Last, [&remove_it](const auto&) { return *remove_it++ > 0; });
}
int main()
{
std::vector<int> v{ 1, 1, 2, 3, 3, 4, 5, 3, 5, 5, 5, 5, 4, 3, 3 };
std::list<int> l{ 1, 1, 2, 3, 3, 4, 5, 3, 5, 5, 5, 5, 4, 3, 3 };
auto equal_to_prev_or_next = [](const int* prev, int el, const int* next) { return (prev && *prev == el) || (next && *next == el); };
v.erase(remove_if_with_prev_and_next(v.begin(), v.end(), equal_to_prev_or_next), v.end());
l.erase(remove_if_with_prev_and_next(l.begin(), l.end(), equal_to_prev_or_next), l.end());
}
Instead of storing all checks in a vector, testing one element could first precompute if the next should be removed. Implementation:
template<class _FwdIt, class _Pr>
inline _FwdIt remove_if_with_prev_and_next_2(_FwdIt _First, _FwdIt _Last, _Pr _Pred)
{
if (_First == _Last) {
return _First;
}
auto first = _First;
auto next = std::next(first);
bool remove = _Pred(nullptr, *first, next == _Last ? nullptr : &*next);
auto get_remove_this_and_test_next = [&remove, &first, &next, &_Last, &_Pred](const auto&) {
bool remove_this = remove;
if (next != _Last) {
auto prev = first;
first = next;
++next;
remove = _Pred(&*prev, *first, next == _Last ? nullptr : &*next);
}
return remove_this;
};
// assume remove_if calls the predicate only once for every element, in order :-(
return std::remove_if(_First, _Last, get_remove_this_and_test_next);
}
int main()
{
std::vector<int> v{ 1, 1, 2, 3, 3, 4, 5, 3, 5, 5, 5, 5, 4, 3, 3 };
std::list<int> l{ 1, 1, 2, 3, 3, 4, 5, 3, 5, 5, 5, 5, 4, 3, 3 };
auto equal_to_prev_or_next = [](const int* prev, int el, const int* next) { return (prev && *prev == el) || (next && *next == el); };
v.erase(remove_if_with_prev_and_next_2(v.begin(), v.end(), equal_to_prev_or_next), v.end());
l.erase(remove_if_with_prev_and_next_2(l.begin(), l.end(), equal_to_prev_or_next), l.end());
}
Both implementations assume that remove_if calls the predicate only once per element and in order. This is sane to assume, but not guaranteed. If you want to be 100% sure that it works, replace the call to remove_if by this possible implementation (Second version).