3

I want to remove element from container with an idempotent approach (i.e, if the element exist, then remove it, otherwise do nothing, and I can do this as many time as I want and the outcome is the same)

As far a I know, doing map::erase is idempotent, even the key is no exist it's also safe. I think set is the same.

Then what about vector (and similair linear container)? I know this works well:

auto it = std::find(vec.begin(), vec.end(), val);

if (it != vec.end())
  vec.erase(it);

But I'm wondering if there is any approach that we don't have to check it == vec.end() manually?

According to cpp document, the behavior of vec.erase(vec.end()) is not defined, which means we can not do vec.erase(std::find(..)). Is there any method will do this check for me so that I can do remove_if_exist with one line code?

tadman
  • 208,517
  • 23
  • 234
  • 262
Ziqi Liu
  • 2,931
  • 5
  • 31
  • 64
  • 3
    Why not [`remove_if`](https://en.cppreference.com/w/cpp/algorithm/remove) instead of this `find`/`erase` approach? Is it that you want to remove one entry and one entry only? You could put that in your "if" clause. – tadman Nov 01 '18 at 18:01
  • 2
    What do you want to do if there are multiple equivalent values, remove all of them or just one? Are you aware of the [erase-remove idiom](https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom)? – Bob__ Nov 01 '18 at 18:03
  • 1
    Possible duplicate of [Erasing elements from a vector](https://stackoverflow.com/questions/347441/erasing-elements-from-a-vector) –  Nov 01 '18 at 18:42
  • "One line code" or "one statement code"? You can get a lot into a `for`... – Tim Randall Nov 01 '18 at 19:11
  • FWIW, starting in C++17 you could write it as `if (auto it = std::find(...); it != vec.end()) vec.erase(it);` – NathanOliver Nov 01 '18 at 19:14
  • @NathanOliver in earlier versions, you should be able to do this: `if ((auto it = std::find(...)) != vec.end()) vec.erase(it);`. Prior to C++11, you would just have to replace `auto` with `vector::iterator` instead – Remy Lebeau Nov 01 '18 at 22:23
  • @RemyLebeau Good call. – NathanOliver Nov 01 '18 at 22:25

3 Answers3

3

The issue here is not one of idempotency. It is one of data access model.

The associative containers (set, map, etc) are different from sequences, because they have "keys" and that's why you can do lookup-by-key and erase operations all in one go.

Sequences don't have keys, so your comparison is unfair. Note that the equivalent operation on an associative container (i.e. lookup by mapped value — ignore set because weirdly its key is its value!) is equally verbose as with a sequence container.

That being said, the erase-remove idiom is indeed annoyingly cumbersome (this is done to separate "ranges" from their parent containers, in as many algorithms as possible; but still, meh). There's no reason remove_and_erase can't exist, but you'll have to make it yourself by combining remove and erase in a utility function.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
3

Then what about vector (and similair linear container)? I know this works well:

You know wrong. Code that you show is not idempotent either, as subsequent calls can modify the same vector multiple times. So really idempotent call would be:

vec.erase( std::remove( vec.begin(), vec.end(), val ), vec.end() );

and it is one liner as you wanted it to be.

Slava
  • 43,454
  • 1
  • 47
  • 90
1
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
   vector<int> vec {1, 2, 3, 4, 5, 6, 7, 8, 9};

   vector<int>::iterator it;

   ((it = find(vec.begin(), vec.end(), 1)) == vec.end()) ? it : vec.erase(it);

   for(int i : vec) 
     cout << i << endl;

   return 0;
}
P. Hinker
  • 299
  • 2
  • 7