🐾 Алгоритмы диапазонов: Алгоритмы модификации
🐾 Алгоритмы диапазонов: Алгоритмы модификации
Разберём copy, transform, fill/generate, а также remove, reverse и unique — и заодно увидим, почему «удаление» в C++ удаляет не сразу.
🍵 ranges::copy / copy_if — копирование
Приёмник — один итератор (куда писать). Под copy буфер должен быть нужного размера; для copy_if берут std::back_inserter — он сам расширяет контейнер.
❗️ copy не терпит перекрытия источника и приёмника (UB). Для пересечения — copy_backward.
⬆️ ranges::move / swap_ranges — перемещение и обмен
std::vector<std::string> src = {"alpha", "beta", "gamma"};
std::vector<std::string> dst(3);
// Перемещение вместо копирования
std::ranges::move(src, dst.begin());
// dst == {"alpha", "beta", "gamma"}
// src содержит валидные, но "опустошённые" строки
// Обмен содержимым двух диапазонов
std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6};
std::ranges::swap_ranges(a, b);
// a == {4, 5, 6}, b == {1, 2, 3}
move переносит ресурсы, а не копирует — дёшево для строк, векторов и прочих «тяжёлых» типов.
❗️ После move источник валиден, но содержимое не определено (valid but unspecified). Пустыми строки не гарантированы — только присвоить заново или уничтожить.
🌳 ranges::transform — преобразование
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> squared(5);
std::ranges::transform(src, squared.begin(), [](int x){ return x * x; });
// squared == {1, 4, 9, 16, 25}
Применяет функцию к каждому элементу и пишет результат в приёмник. Источник не меняется.
🍩 ranges::fill / generate — заполнение
std::vector<int> dst(5);
std::ranges::fill(dst, 0); // {0, 0, 0, 0, 0}
std::ranges::generate(dst, [n = 0]() mutable { return n++; });
// {0, 1, 2, 3, 4}
fill кладёт одно и то же значение, generate зовёт функцию на каждый элемент — удобно для счётчиков, случайных чисел или любой генерации «на лету».
🥳 ranges::remove — удаление (erase-remove стал проще)
std::vector<int> data = {1, 2, 3, 2, 4, 2, 5};
// Старый способ:
data.erase(std::remove(data.begin(), data.end(), 2), data.end());
// Новый способ (C++20):
auto [first, last] = std::ranges::remove(data, 2);
data.erase(first, last);
// data == {1, 3, 4, 5}
Важный момент: remove ничего физически не удаляет — он лишь сдвигает «выжившие» элементы в начало и возвращает поддиапазон «мусорного хвоста». Реально обрезает контейнер только последующий erase.
❗️ Если контейнер свой (а не чужой диапазон), ещё короче — свободная функция std::erase(data, 2): одна строка вместо пары.
🍿 ranges::reverse — разворот
std::vector<int> data = {1, 3, 4, 5};
std::ranges::reverse(data); // {5, 4, 3, 1}
Переворачивает диапазон на месте, без дополнительной памяти.
🎵 ranges::unique — удаление соседних дубликатов
std::vector<int> dups = {1, 1, 2, 3, 3, 3, 4};
auto [u_first, u_last] = std::ranges::unique(dups);
dups.erase(u_first, u_last);
// dups == {1, 2, 3, 4}
Как и remove, возвращает поддиапазон хвоста и требует erase для реальной очистки.
❗️ Убирает только соседние повторы — чтобы вычистить все дубликаты, диапазон сначала надо отсортировать.
🍴 Проекции — работают и здесь
struct Task {
std::string name;
int priority;
};
std::vector<Task> tasks = {
{"Код", 1}, {"Почта", 0}, {"Деплой", 2}, {"Черновик", 0}
};
// Удалить все задачи с priority == 0 — без лямбды
auto [first, last] = std::ranges::remove(tasks, 0, &Task::priority);
tasks.erase(first, last);
// Остались {"Код", 1} и {"Деплой", 2}
Проекция — последний аргумент: remove, copy_if, transform и другие умеют работать «по полю».
❗️ Но проекция — не предикат. У remove это лишь сравнение на равенство; для условий сложнее (priority > 0) нужен remove_if.
Разберём copy, transform, fill/generate, а также remove, reverse и unique — и заодно увидим, почему «удаление» в C++ удаляет не сразу.
🍵 ranges::copy / copy_if — копирование
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(5);
std::ranges::copy(src, dst.begin()); // dst == {1, 2, 3, 4, 5}
// Копирование с условием
std::vector<int> evens;
std::ranges::copy_if(src, std::back_inserter(evens),
[](int x) { return x % 2 == 0; });
// evens == {2, 4}
Приёмник — один итератор (куда писать). Под copy буфер должен быть нужного размера; для copy_if берут std::back_inserter — он сам расширяет контейнер.
❗️ copy не терпит перекрытия источника и приёмника (UB). Для пересечения — copy_backward.
⬆️ ranges::move / swap_ranges — перемещение и обмен
std::vector<std::string> src = {"alpha", "beta", "gamma"};
std::vector<std::string> dst(3);
// Перемещение вместо копирования
std::ranges::move(src, dst.begin());
// dst == {"alpha", "beta", "gamma"}
// src содержит валидные, но "опустошённые" строки
// Обмен содержимым двух диапазонов
std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6};
std::ranges::swap_ranges(a, b);
// a == {4, 5, 6}, b == {1, 2, 3}
move переносит ресурсы, а не копирует — дёшево для строк, векторов и прочих «тяжёлых» типов.
❗️ После move источник валиден, но содержимое не определено (valid but unspecified). Пустыми строки не гарантированы — только присвоить заново или уничтожить.
🌳 ranges::transform — преобразование
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> squared(5);
std::ranges::transform(src, squared.begin(), [](int x){ return x * x; });
// squared == {1, 4, 9, 16, 25}
Применяет функцию к каждому элементу и пишет результат в приёмник. Источник не меняется.
🍩 ranges::fill / generate — заполнение
std::vector<int> dst(5);
std::ranges::fill(dst, 0); // {0, 0, 0, 0, 0}
std::ranges::generate(dst, [n = 0]() mutable { return n++; });
// {0, 1, 2, 3, 4}
fill кладёт одно и то же значение, generate зовёт функцию на каждый элемент — удобно для счётчиков, случайных чисел или любой генерации «на лету».
🥳 ranges::remove — удаление (erase-remove стал проще)
std::vector<int> data = {1, 2, 3, 2, 4, 2, 5};
// Старый способ:
data.erase(std::remove(data.begin(), data.end(), 2), data.end());
// Новый способ (C++20):
auto [first, last] = std::ranges::remove(data, 2);
data.erase(first, last);
// data == {1, 3, 4, 5}
Важный момент: remove ничего физически не удаляет — он лишь сдвигает «выжившие» элементы в начало и возвращает поддиапазон «мусорного хвоста». Реально обрезает контейнер только последующий erase.
❗️ Если контейнер свой (а не чужой диапазон), ещё короче — свободная функция std::erase(data, 2): одна строка вместо пары.
🍿 ranges::reverse — разворот
std::vector<int> data = {1, 3, 4, 5};
std::ranges::reverse(data); // {5, 4, 3, 1}
Переворачивает диапазон на месте, без дополнительной памяти.
🎵 ranges::unique — удаление соседних дубликатов
std::vector<int> dups = {1, 1, 2, 3, 3, 3, 4};
auto [u_first, u_last] = std::ranges::unique(dups);
dups.erase(u_first, u_last);
// dups == {1, 2, 3, 4}
Как и remove, возвращает поддиапазон хвоста и требует erase для реальной очистки.
❗️ Убирает только соседние повторы — чтобы вычистить все дубликаты, диапазон сначала надо отсортировать.
🍴 Проекции — работают и здесь
struct Task {
std::string name;
int priority;
};
std::vector<Task> tasks = {
{"Код", 1}, {"Почта", 0}, {"Деплой", 2}, {"Черновик", 0}
};
// Удалить все задачи с priority == 0 — без лямбды
auto [first, last] = std::ranges::remove(tasks, 0, &Task::priority);
tasks.erase(first, last);
// Остались {"Код", 1} и {"Деплой", 2}
Проекция — последний аргумент: remove, copy_if, transform и другие умеют работать «по полю».
❗️ Но проекция — не предикат. У remove это лишь сравнение на равенство; для условий сложнее (priority > 0) нужен remove_if.
Просмотры: 210