[1 container] 9 Carefully choose the method of deleting elements

Posted by francoisp on Fri, 14 Jan 2022 01:24:34 +0100

1 delete the specified element in the container

Assuming the STL container c, you need to delete all elements in c with a value of 1963.

Container<int> c;

Note that the method of doing this varies with the container type.

(1) Continuous memory container vector, deque or string

Use the erase remove idiom:

// remove moves the elements in the specified interval until all "do not delete" elements are at the beginning of the interval
c.erase(remove(c.begin(), c.end(), 1963), c.end());

(2) list

c.remove(1963);

(3) Standard Association container set, multiset, map or multimap

c.erase(1963);

Time complexity log time. The sequence container is based on the remove method, and the time complexity is linear time.

2 delete each object whose specified discriminant returns true

Discriminant:

bool badValue(int x);

(1) Continuous memory container vector, deque or string

// remove moves the elements in the specified interval until all "do not delete" elements are at the beginning of the interval
c.erase(remove_if(c.begin(), c.end(), badValue), c.end());

 (2) list

c.remove_if(badValue);

(3) Standard Association container set, multiset, map or multimap

[1] Simple but inefficient method

Using remove_copy_if copy the values we need into a new container, and then exchange the contents of the new container and the original container:

AssocContainer<int> c;
...
// A temporary container that holds values that are not deleted
AssocContainer<int> goodValues;
// Copy the values that are not deleted from c to goodValues
remove_copy_if(c.begin(), c.end(), inserter(goodValues, goodValues.end()), badValue);
// Exchange c and goodValues
c.swap(goodValues);

The disadvantage of this method is that all elements that are not deleted need to be copied.

[2] Delete elements directly from the original container to improve efficiency

Because the associated container does not provide a similar remove_if member function, so we must write a loop to traverse the elements in c and delete the elements during the traversal.

The first thing many programmers think of is:

AssocContainer<int> c;
...
for (AssocContainer<int>::iterator i = c.begin(); i!=c.end(); ++i) {
    if (badValue(*i)) {
        c.erase(i);
    }
}

There are bug s in the above code, which will lead to uncertain behavior. When an element in a container is deleted, all iterators pointing to that element become invalid. Once c.erase(i) returns, i becomes an invalid value.

To avoid this problem, we need to ensure that an iterator points to the next element in c before calling erase. The simplest way is to use the suffix increment for i when calling:

AssocContainer<int> c;
...
for (AssocContainer<int>::iterator i = c.begin(); i!= c.end(); ) {
    if (badValue(*i)) {
        c.erase(i++);
    } else {
        ++i;
    }
}

The value of expression i + + is the old value of I. as a side effect, I is incremented. We pass the old I to erase and increment I before erase starts to execute. I is valid.

3 not only delete the element that makes badValue return true, but also write a message to the log every time you delete the element

(1) Standard Association container set, multiset, map or multimap

It is relatively simple. You only need to make simple modifications to the previous cycle:

ofstream logFile;
AssocContainer<int> c;
...
for (AssocContainer<int>::iterator i = c.begin(); i!=c.end(); ) {
    if (badValue(*i)) {
        logFile << "Erasing " << *i << "\n";
        c.erase(i++);
    } else {
        ++i;
    }
}

(2) Continuous memory container vector, deque or string

The erase remove usage can no longer be used because there is no way to write information to the log file with erase or remove.

A loop like an associative container cannot be used because for continuous memory containers, calling erase invalidates not only the iterators pointing to the deleted element, but also all iterators after the deleted element.

For continuous memory containers, use the return value of erase, which points to the valid iterator of the next element immediately following the deleted element.

for (SeqContainer<int>::iterator i = c.begin(); i != c.end();) {
    if (badValue(*i)) {
        logFile << "Erasing " << *i << "\n";
        // The return value of erase is assigned to i to make i valid
        i = c.erase(i);
    } else {
        ++i;
    }
}

(3) list

The Convention is to take the same approach to list s as continuous memory containers