Удаление элементов и идиома Remove-Erase Idiom

Последнее обновление: 31.03.2023

Идиома remove-erase idiom призвана решить проблему удаления элементов из контейнера, поскольку данная проблема может представлять нетривиальную задачу, чреватую возникновением ошибок. Данная идиома предполагает применение алгоритма remove() или remove_if(), за которым следует вызов функции erase() контейнера.

При применении алгоритмов remove() и remove_if() те элементы, которые надо сохранить, помещаются в начало контейнера, а функции remove() и remove_if() возвращают итератор на первый удаляемый элемент. Затем этот итератор передается в функцию erase(), которая собственно и удаляет элементы. Реализация идиомы:

#include <iostream>
#include <vector>
#include <algorithm>

void print (const std::vector<int>& data)
{
    for(const auto& n: data)
    {
        std::cout << n << "\t";
    }
    std::cout << std::endl;
}

bool is_negative(int n){ return n < 0;}

int main()
{
    std::vector<int> numbers {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5};  

    // применяем алгоритм remove_if() для удаления всех элементов, которые не соответствуют условию
    auto iter{ std::remove_if(begin(numbers), end(numbers), is_negative) };
     print(numbers);   // 0       1       2       3       4       5       1       2       3       4       5

    // удаляем все элементы, начиная с итератора first_to_erase
    numbers.erase(iter, end(numbers));

    print(numbers);   // 0       1       2       3       4       5
}

Здесь для примера удаляем из вектора все отрицательные числа. Для этого сначала вызываем функцию std::remove_if():

auto iter{ std::remove_if(begin(numbers), end(numbers), is_negative) };
print(numbers);

В качестве первого и второго параметров она принимает итераторы на начало и конец диапазона, из которого надо удалить числа. Здесь диапазон определяется итераторами на начало и конец вектора. В качестве третьего параметра передается условие. Условие должно представлять функцию, которая принимает некоторое значение и возвращает значение типа bool - true, если значение соответствует условию, и false - если не соответствует. В данном случае в качестве такого условия передаем функцию is_negative, которая вычисляет, является ли число отрицательным. То есть мы удаляем отрицательные числа.

Для вывода вектора на консоль применяется функция print. После выполнения remove_if на консоль будет выведено

0       1       2       3       4       5       1       2       3       4       5

В результате remove_if() просто перемещает все элементы, которые нужно сохранить (0 и положительные числа), в начало диапазона. При этом часть из этих чисел остается в конце вектора, но это не имеет значения, поскольку эта часть вектора будет удалена. А сама функция возвращает итератор iter, который указывает на первый удаляемый элемент. Далее удаляем все элементы, которые начинаются с этого итератора:

numbers.erase(iter, end(numbers));
print(numbers);

Теперь консольный вывод будет следующим:

0       1       2       3       4       5

std::erase_if()

Поскольку безопасное удаление из контейнеров представляет довольно часто встречаемую задачу, то начиная со стандарта C++20 в язык С++ были добавлены функции std::erase() и std::erase_if()

Функция std::erase() удаляет отдельное значение из контейнера (не применяется к std::set и std::map):

std::erase(Container, Value)

Функция std::erase_if() удаляет значения из контейнера, которые соответствуют условию:

std::erase_if(Container, Function)

Так, перепишем предыдущий пример с помощью функции std::erase_if:

#include <iostream>
#include <vector>

void print (const std::vector<int>& data)
{
    for(const auto& n: data)
    {
        std::cout << n << "\t";
    }
    std::cout << std::endl;
}

bool is_negative(int n){ return n < 0;}

int main()
{
    std::vector<int> numbers {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5};  

    std::erase_if(numbers, is_negative);
    print(numbers);   // 0       1       2       3       4       5
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850