1

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?

  • you can access previous iterator by `v.begin() + std::distance(v.begin(), it) - 1`, but aware of out of bounds – Cwift Dec 06 '22 at 06:17
  • but using v.begin() inside the function passed to remove-if is possible only if std::vector v is also passed as an argument, besides the current iterator item – New programmer Dec 06 '22 at 06:27
  • With a lambda, you can capture `v` by reference. – kotatsuyaki Dec 06 '22 at 06:40
  • 3
    It seems like you should use raw loop to do this, there are different case whether the removed element will effect the next element. – Cwift Dec 06 '22 at 06:43
  • 2
    In such complicated case it can be better to use two pass approach. First pass uses usual loop and generates `std::vector` with flags for which elements should be removed, and second pass will remove marked elements based on this vector. – sklott Dec 06 '22 at 08:36

1 Answers1

1
  1. As pointed out in the comments, you need an intermediate container to store if the elements have to be removed, because if the previous has been removed, it cannot be used to check if the next has to be removed.
  2. Previous and next may fall out of the container. The predicate function (aka test or check) can take pointers instead of references, so that nullptr can signal an out-of-container element.

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).

Wyrzutek
  • 605
  • 1
  • 5
  • 13