Алгоритмы стандартной библиотеки C++
Стандартная библиотека содержит большое количество алгоритмов для работы с контейнерами стандартной библиотеки. Доступные инструменты покрывают значительную часть встречающихся алгоритмических задач. Использование стандартных алгоритмов вместо их самостоятельной реализации является хорошим стилем программирования по следующим причинам:
- Экономия времени. Мы не тратим время на реализацию и отладку алгоритма.
- Гарантия отсутствия ошибок в логике работы алгоритма. Алгоритмы стандартной библиотеки протестированы многими программистами.
- Лаконичность и выразительность кода. Вместо некоторого количества строчек, которые выполняют неочевидные манипуляции, мы видим название хорошо документированного алгоритма.
Мы рассмотрим лишь некоторые из доступных алгоритмов. Полный список можно найти в документации. Мы рекомендуем всегда проверять наличие стандартного решения при встрече с алгоритмической задачей.
iota, for_each и transform
Большое количество циклов for в коде, который выполняет манипуляции со структурами данных, обычно говорит о недостаточном использовании стандартных алгоритмов. Так, если необходимо применить некоторую функцию ко всем элементам контейнера, то можно рассмотреть использование алгоритма for_each .
Решим следующую задачу: вывести в стандартный поток квадраты натуральных чисел от 1 до 100. Следующий код показывает, что эта задача может быть решена в четырех строчках кода и без явного использования циклов:
#include #include #include // iota #include // for_each using namespace std; int main() vectorint> v(100); iota(v.begin(), v.end(), 1); // v = [1, 2, 3, . 100] for_each(v.begin(), v.end(), [](int& a)a = a*a;>); // v = [1, 4, 9, . 10000] for_each(v.begin(), v.end(), [](int a)cout <a <' ';>); return 0; >
Мы воспользовались алгоритмом iota из библиотеки , чтобы проинициализировать массив набором последовательных целых чисел. Затем мы два раза использовали алгоритм for_each : для вычисления квадратов и для вывода значений в стандартный поток.
Третьим аргументом алгоритм for_each принимает функцию одного аргумента. Тип аргумента должен соответствовать типу элементов контейнера. Вместо обычной функции бывает удобно передать лямбда-выражение, что мы и сделали оба раза в этом примере. Лямбда-выражение позволяет определить функцию в месте ее использования. Квадратные скобки [] указывают на начало лямбда-выражения; в круглых скобках указываются аргументы выражения; в фигурных скобках содержится тело лямбда-выражения.
Модифицируем немного нашу задачу. Предположим, что мы не хотим изменять исходный вектор, а значения квадратов хотим сохранить в другом векторе. Алгоритм transform позволяет выполнить такое преобразование:
#include #include #include // iota #include // for_each, transform using namespace std; int main() vectorint> source(100); iota(source.begin(), source.end(), 1); // v = [1, 2, 3, . 100] vectorint> target(source.size()); transform(source.begin(), source.end(), target.begin(), [](int& a)return a*a;>); // v = [1, 4, 9, . 10000] for_each(target.begin(), target.end(), [](int a)cout <a <' ';>); return 0; >
Третьим аргументом алгоритм transform принимает итератор на место целевого контейнера, с которого нужно начать заполнять значения. Обратите внимание, что мы заранее инициализировали вектор target нужной длины.
all_of, any_of, none_of
Довольно часто возникает задача проверки какого-либо условия для всех объектов контейнера. Здесь на помощь приходят алгоритмы all_of , any_of и none_of с очевидным поведением, которые принимают диапазон значений и унарный предикат — функцию одного аргумента, которая возвращает true или false . Так, например, можно проверить содержит ли множество хотя бы один отрицательный элемент:
setdouble> s1.1, -0.9, 2.4, 10.1, 3.1415>; bool neg_in_set = any_of(s.begin(), s.end(), [](double x)return x 0;>); // true
Вторая строчка этого примера не поменяется, если вместо контейнера set будет использован другой контейнер, например, list , vector , array или unordered_set .
count, count_if, find, find_if
Алгоритм count позволяет посчитать количество элементов в контейнере, равных заданному. Модификация этого алгоритма count_if подсчитывает количество элементов, удовлетворяющих определенному условию. Рассмотрим следующий пример: мы имеем дело с историей авторизации пользователей на сайте, которая хранится в виде вектора строк. Каждая строка — это логин пользователя. Подсчитаем сколько раз авторизовывался пользователь с логином david:
vectorstring> history = /*. */>; size_t david_count = count(history.begin(), history.end(), "david");
Если нам захочется удалить запись для логина david, мы можем это сделать с помощью алгоритма find и метода vector::erase :
if (auto item = find(history.begin(), history.end(), "david"); item != history.end()) history.erase(item); >
Алгоритм find возвращает итератор на найденный элемент. Версия алгоритма find_if позволяет найти первый элемент, удовлетворяющий некоторому условию.
Как и другие алгоритмы, find и count могут работать с контейнерами разных типов. Они проходят переданный диапазон значений последовательно, начиная с первого элемента. Использование такого подхода для контейнеров set и map — плохая идея, ведь они созданы для того чтобы выполнять поиск объектов быстрее. Это общее правило: если контейнер имеет метод, аналогичный общему алгоритму, то следуем использовать метод контейнера. В большинстве случаев это даст выигрыш в производительности.
sort, stable_sort, nth_element
Алгоритмы сортировки — это важный и интересный раздел теории алгоритмов. Работать с отсортированными элементами во многих ситуациях удобнее, в частности, сложность поиска элементов становится логарифмической вместо линейной. Стандартная библиотека C++ предлагает алгоритмы sort и stable_sort , которые выполняют сортировку за время, пропорциональное N log(N), где N — количество элементов массива. Стабильная сортировка stable_sort при этом гарантирует, что равные объекты не меняют своего относительного положения в контейнере.
Рассмотрим простой пример сортировки:
vectorstring> v "David", "Ivan", "Adam", "Dmitry">; sort(v.begin(), v.end()); // ["Adam", "David", "Dmitry", "Ivan"]
bool string_cmp(const string& lhs, const string& rhs) return lhs.size() > rhs.size(); > vectorstring> v "David", "Ivan", "Adam", "Dmitry">; stable_sort(v.begin(), v.end(), string_cmp); // ["Dmitry", "David", "Ivan", "Adam"]
Мы использовали стабильную версию сортировки. В этом случае Ivan гарантировано окажется левее Adam в отсортированном векторе.
Оказывается, что задача поиска n-го элемента (как если бы элементы стояли по порядку по какому-либо признаку) может быть решена быстрее, чем сортировка всего массива — за линейное время. Стандартная библиотека предлагает алгоритм nth_element для решения этой задачи.
lower_bound, upper_bound, binary_search
Коль скоро мы научились получать отсортированные массивы, рассмотрим алгоритмы для поиска элементов в них. Алгоритмы lower_bound и upper_bound позволяют найти в отсортированном массиве первый элемент не меньше данного и первый элемент больше данного, соответственно. Эти алгоритмы возвращают итератор, соответствующий найденному элементу.
Алгоритм binary_search проверяет, есть ли в отсортированном массиве данный элемент и возвращает true или false в зависимости от результата поиска.
Все три алгоритма выполняются за логарифмическое время.
Резюме
В этом материале мы рассмотрели примеры использования нескольких основных алгоритмов стандартной библиотеки C++. Обсудили, что применение стандартных алгоритмов является хорошим стилем программирования, позволяет писать код быстрее, и делает его более легким для прочтения.
Полезные алгоритмы стандартной библиотеки не ограничиваются рассмотренными выше. Мы рекомендуем посмотреть на полный список доступных алгоритмов, среди которых можно обратить внимание на алгоритмы copy , remove , generate и partition , которые вполне могут пригодиться.
Конечно, мы не ожидаем, что после прочтения этого материала вы сразу начнете свободно применять разнообразные алгоритмы. Только с практикой использование алгоритмов становится естественным и полезным инструментом разработки.
Документация
- https://en.cppreference.com/w/cpp/algorithm
- http://www.cplusplus.com/reference/algorithm/
- https://en.cppreference.com/w/cpp/language/lambda
Алгоритмы диапазонов C++20 — 11 модифицирующих операций

В предыдущей статье серии «Диапазоны» я рассмотрел основы и некоторые немодифицирующие операции. Сегодня пришло время таких алгоритмов, как transform , copy , generate , shuffle и многих других… даже rotate . Подробности — к старту курса по разработке на С++.
Прежде чем мы начнём
Ключевые наблюдения об алгоритмах std::ranges :
- Алгоритмы диапазонов определяются в заголовке , а инфраструктура диапазонов и их основные типы — в заголовке .
- Обычно есть как минимум две перегрузки для алгоритмов диапазона: с парой итераторов и перегрузка с одним аргументом диапазона.
- Версия, которая возвращает поддиапазон или итератор и принимает диапазон, возвращает заимствованный диапазон или заимствованный итератор. Это помогает обнаруживать итераторы для временных диапазонов.
- Версии диапазона имеют проекции, что даёт больше гибкости; например, можно выполнить сортировку по некоторым выбранным элементам или перед сравнением выполнить дополнительные преобразования.
- В версии с диапазонами нет опции параллельного выполнения (нельзя передать политику выполнения std::execution ).
- Алгоритмы диапазонов, как и стандартные алгоритмы C++20, также являются constexpr .
- Начиная с версии C++20, больше нет алгоритмов числовых диапазонов, соответствующих заголовку .
Примеры ниже показывают стандартный алгоритм и альтернативную версию с диапазонами. Они иллюстрируют некоторые основные концепции. Я стремился не использовать расширенную композицию диапазонов или представления. Мы рассмотрим их в порядке, указанном в cppreference/algorithms.
В этой части рассмотрим некоторые алгоритмы, позволяющие изменять последовательность, такие как копирование, удаление, преобразование или создание элементов.
- Первая статья о «7 немодифицирующих операциях»
copy_if
У основного алгоритма copy много вариантов: copy_if , copy_n и даже copy_backward .
В простой форме copy_if определяется так:
// skipping all concept/templates declaration constexpr copy_if_result, O> copy_if( R&& r, O result, Pred pred, Proj proj = <> );
Посмотрим на пример:
#include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; >; std::ostream& operator <<(std::ostream& os, const Product& p) < os << p.name_ << ", " << p.value_; return os; >int main() < const std::vectorprods < < "box", 10.0 >, , , < "car", 1000.0 >, , >; // standard version: std::copy_if(begin(prods), end(prods), std::ostream_iterator(std::cout, "; "), [](const Product& p)< return !p.name_.starts_with("none"); >); std::cout << '\n'; // ranges version: std::ranges::copy_if(prods, std::ostream_iterator(std::cout, "; "), [](const Product& p)< return !p.name_.starts_with("none"); >); >
В примере я копирую элементы из вектора в выходной поток. Кроме того, на этапе фильтрации я выбираю только товары не «none». Поскольку в поток копируются целые элементы, мне пришлось реализовать operator< для класса Product .
Благодаря проекциям я также мог написать такой вариант кода:
std::ranges::copy_if(prods, std::ostream_iterator(std::cout, "; "), [](const std::string& name)< return !name.starts_with("none"); >, &Product::name_);
Этот код немного длиннее, но теперь предикат принимает string , а не весь объект Product .
fill
#include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; Product& operator=(int i) < name_ += std::to_string(i); return *this; >>; std::ostream& operator <<(std::ostream& os, const Product& p) < os << p.name_ << ", " << p.value_; return os; >int main() < std::vectorprods>; // standard version: std::fill(begin(prods), end(prods), 4); std::ranges::copy(prods, std::ostream_iterator(std::cout, "; ")); std::cout << '\n'; // ranges version: std::ranges::fill(prods, 2); std::ranges::copy(prods, std::ostream_iterator(std::cout, "; ")); >
Алгоритм fill проходит по диапазону, а затем выполняет присвоение с переданным вами value . Тип value может отличаться от типа элементов в контейнере.
while (first != last) *first++ = value;
В примере я использовал класс с пользовательским оператором преобразования, им можно воспользоваться для изменения члена данных name_ на основе интегрального входного значения.
generate
В то время как fill() использует одно и то же значение для присвоения всем элементам, generate() использует функциональный объект для генерации значения. В этом примере можно смоделировать генерацию iota :
#include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; Product& operator=(int i) < name_ += std::to_string(i); return *this; >>; std::ostream& operator <<(std::ostream& os, const Product& p) < os << p.name_ << ", " << p.value_; return os; >int main() < std::vectorprods>; // standard version: std::generate(begin(prods), end(prods), [v = 0]() mutable < return v++; >); std::ranges::copy(prods, std::ostream_iterator(std::cout, "; ")); std::cout ); std::ranges::copy(prods, std::ostream_iterator(std::cout, "; ")); >
Box 0, 1; Box 1, 1; Box 2, 1; Box 3, 1; Box 4, 1; Box 5, 1; Box 6, 1; Box 01, 1; Box 12, 1; Box 23, 1; Box 34, 1; Box 45, 1; Box 56, 1; Box 67, 1;
transform
transform() — надёжный алгоритм, имеющий множество вариаций.
В простой форме он выглядит так:
transform( R&& r, O result, F op, Proj proj = <> );
Он принимает диапазон r , а затем использует op для преобразования элементов из этого диапазона и вывода в result , который является итератором.
Вот простой пример:
#include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; >; int main() < std::vectorprods>; // standard version: std::transform(begin(prods), end(prods), begin(prods), [v = 0](const Product &p) mutable < return Product < p.name_ + std::to_string(v++), 1.0>; >); for (auto &p : prods) std::cout ; >, &Product::name_); for (auto &p : prods) std::cout
Box 0, Box 1, Box 2, Box 3, Box 4, Box 5, Box 6, Box 00, Box 11, Box 22, Box 33, Box 44, Box 55, Box 66,
Выше преобразуется тот же контейнер, но к каждому имени добавляются числа, сгенерированные с помощью функции.
Также есть версия, которая берет два диапазона и объединяет их с помощью бинарной операции:
transform( R1&& r1, R2&& r2, O result, F binary_op, Proj1 proj1 = <>, Proj2 proj2 = <> );
Этой версией можно воспользоваться для «объединения» двух контейнеров и получения одного значения:
std::vector prods>; std::vector numbers; std::ranges::transform(prods, numbers, begin(prods), [](const Product& p, int v) < return Product < p.name_ + std::to_string(v), 1.0>; >); for (auto &p : prods) std::cout
remove
В C++20 есть способ удаления и стирания элементов из различных контейнеров эффективнее. Смотрите std::erase_if , набор перегруженных функций для согласованного удаления контейнера. Подробности — в моей статье: 20 небольших, но удобных функций C++20 — согласованное удаление контейнеров.
Для полноты картины сравним все три версии:
#include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; >; int main() < const std::vectorprods < < "box", 10.0 >, , , , < "car", 1000.0 >, , >; auto printCont = [](const std::vector& cont) < for (auto &p : cont) std::cout ; std::cout ; // standard version: auto tempProds = prods; tempProds.erase(std::remove_if(tempProds.begin(), tempProds.end(), checkNoPrefix), tempProds.end()); printCont(tempProds); // ranges version: tempProds = prods; tempProds.erase(std::ranges::remove_if(tempProds, checkNoPrefix).begin(), tempProds.end()); printCont(tempProds); // C++20 version: tempProds = prods; std::erase_if(tempProds, checkNoPrefix); printCont(tempProds); >
Версия ranges сокращает этот вызов:
tempProds.erase(std::remove_if(tempProds.begin(), tempProds.end(), checkNoPrefix), tempProds.end());
tempProds.erase(std::ranges::remove_if(tempProds, checkNoPrefix).begin(), tempProds.end());
Но, на мой взгляд, это выглядит не намного лучше. ranges::remove_if возвращает поддиапазон, поэтому всё равно нужно прописать его begin() и, возможно, end() .
Гораздо проще написать так:
std::erase_if(tempProds, checkNoPrefix);
replace
Как заменить элементы внутри контейнера:
#include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; friend bool operator==(const Product& a, const Product& b) < return a.name_ == b.name_ && abs(a.value_ - b.value_) < 0.0001; >>; std::ostream& operator <<(std::ostream& os, const Product& p) < os << p.name_ << ", " << p.value_; return os; >int main() < std::vectorprods < < "box", 10.0 >, , , < "car", 1000.0 >, , , , < "invalid", -10.0 >>; std::ostream_iterator out_iter(std::cout, "; "); // standard version: std::cout << "before: \n"; std::copy(begin(prods), end(prods), out_iter); std::replace(begin(prods), end(prods), Product, Product); std::cout << "\nafter: \n"; std::copy(begin(prods), end(prods), out_iter); std::cout << '\n'; // ranges version: std::cout << "before: \n"; std::ranges::copy(prods, out_iter); std::ranges::replace(prods, "invalid", Product, &Product::name_); std::cout
before: box, 10; tv, 100; rocket, 10000; car, 1000; toy, 40; none, 0; invalid, 0; invalid, -10; after: box, 10; tv, 100; rocket, 10000; car, 1000; toy, 40; default, 10; invalid, 0; invalid, -10; before: box, 10; tv, 100; rocket, 10000; car, 1000; toy, 40; default, 10; invalid, 0; invalid, -10; after: box, 10; tv, 100; rocket, 10000; car, 1000; toy, 40; default, 10; default, 10; default, 10;
Интересно, что в стандартной версии значение сравнивается с объектами в контейнере:
for (; first != last; ++first) < if (*first == old_value) < *first = new_value; >>
Вот почему мне пришлось определить оператор сравнения == (или "космический корабль" для большей гибкости).
Сравнение немного отличается, поэтому в версии с диапазонами можно использовать проекцию:
for (; first != last; ++first) < if (old_value == std::invoke(proj, *first)) < *first = new_value; >>
В этом примере необходимости в операторе == нет, ведь строки можно сравнивать напрямую. Это даёт больше гибкости, поскольку так можно найти больше недопустимых значений (значение value_ теперь не проверяется, чтобы найти и исправить — 0.0 или -10.0 ).
reverse
Попробуем вариант с reverse-копией с выводом в поток:
#include #include #include #include int main() < const std::vector numbers < "one", "two", "three", "four", "five", "six" >; auto outStream = std::ostream_iterator(std::cout, "; "); // standard version: std::copy(begin(numbers), end(numbers), outStream); std::coutone; two; three; four; five; six; six; five; four; three; two; one; Ranges one; two; three; four; five; six; six; five; four; three; two; one;Как видите, работать с этой версией очень просто.
rotate
На этот раз давайте поработаем со словами и попробуем развернуть их:
#include #include #include #include #include int main() < std::vectorwords < "hello", "in", "the", "wonderful", "world", "of", "c++", "programming", >; std::ostream_iterator out(std::cout, " "); // standard version: std::ranges::copy(words, out); std::cout >Пример начинается с предложения и поворачивает его так, что слово "the" становится первым словом. Позже в версии с диапазонами попытаемся найти первое слово исходного предложения, а затем снова сдвинуть его, чтобы добраться до начала.
hello in the wonderful world of c++ programming in the wonderful world of c++ programming hello "hello" is now at pos 7 hello in the wonderful world of c++ programming "in" is now at pos 1shuffle
Напомню, что std::random_shuffle объявлен устаревшим и удалён в C++17. Начиная с C++11, лучше всего использовать std::shuffle или std::ranges::shuffle , которые принимают в качестве параметра объект случайного генератора, а не полагаются на rand() .
Давайте посмотрим на простой пример:
#include #include #include #include #include int main() < std::vectorwords < "box", "tv", "car", "bricks", "game", "ball" >; std::mt19937 rng()>; auto print = [](std::string_view str, const auto& cont) < std::cout ; print("before", words); // the standard version: std::shuffle(begin(words), end(words), rng); print("after ", words); // the ranges version: // the standard version: std::ranges::shuffle(words, rng); print("after ", words); >sample
std::sample — относительно новый алгоритм, доступный с C++17. Он позволяет с равномерной вероятностью случайно выбрать n элементов из последовательности. Это не аналог constexpr . Давайте посмотрим на пример:
#include #include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; >; int main() < const std::vectorprods < < "box", 10.0 >, , , < "car", 1000.0 >, , , < "book", 45.0>, , >; std::mt19937 rng()>; const size_t firstRoundCount = 4; const size_t secondRoundCount = 2; // the standard version: std::vector selected; std::sample(begin(prods), end(prods), std::back_inserter(selected), firstRoundCount, rng); std::cout << firstRoundCount << " selected products: \n"; for (const auto &elem : selected) std::cout << elem.name_ << '\n'; // the ranges version: std::vectoronlyTwo; std::ranges::sample(selected, std::back_inserter(onlyTwo), secondRoundCount, rng); std::coutunique
Алгоритм unique() позволяет очистить последовательную группу эквивалентных элементов. Например, из вы можете удалить все дубликаты и получить . Обратите внимание, что не все 5 были удалены, а только те, что были в той же «группе».
Давайте посмотрим на следующий пример, где я удаляю дубликаты слов:
#include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; >; int main() < std::vectorprods < < "box", 20.0>, , , < "box", 10.0 >, , , < "car", 1000.0 >, , , , >; auto print = [](std::string_view str, const std::vector& cont) < std::cout ; print("before: ", prods); auto ret = std::ranges::unique(prods, <>, &Product::name_); prods.erase(ret.begin(), ret.end()); print("after unique: ", prods); std::ranges::sort(prods, <>, &Product::name_); print("after sort: ", prods); ret = std::ranges::unique(prods, <>, &Product::name_); prods.erase(ret.begin(), ret.end()); print("another unique:", prods); >before: : box, box, toy, box, tv, tv, car, box, toy, cake, after unique: : box, toy, box, tv, car, box, toy, cake, after sort: : box, box, box, cake, car, toy, toy, tv, another unique:: box, cake, car, toy, tv,Как видите, этот пример не охватывает стандартную версию, а фокусируется только на ranges::unique .
После первого запуска unique() вектор prods модифицируется таким образом, что удаляемые элементы передаются в конец контейнера. Более того, они имеют неопределённое значение. Вот почему я использовал erase , чтобы удалить эти элементы из контейнера. Объект ret содержит поддиапазон, указывающий на первый «удалённый» элемент и конец входного диапазона.
После первой «итерации» все еще есть некоторые повторяющиеся элементы, но они не принадлежат одной и той же «группе». Чтобы исправить это, можно отсортировать элементы (я использую проекцию, чтобы смотреть только на элемент данных name_ ). После сортировки элементов можно подчистить остальные дубликаты. Конечно, сортировку можно сделать перед всей очисткой.
Заключение
Как видите, с помощью версий с диапазонами можно упростить код и передать всю последовательность, весь контейнер. Во многих случаях это делает код куда более читаемым.
Научим вас аккуратно работать с данными, чтобы вы прокачали карьеру и стали востребованным IT-специалистом.
- Профессия Fullstack-разработчик на Python (16 месяцев)
- Профессия Data Scientist (24 месяца)
Краткий каталог курсов
Data Science и Machine Learning
- Профессия Data Scientist
- Профессия Data Analyst
- Курс «Математика для Data Science»
- Курс «Математика и Machine Learning для Data Science»
- Курс по Data Engineering
- Курс «Machine Learning и Deep Learning»
- Курс по Machine Learning
Python, веб-разработка
- Профессия Fullstack-разработчик на Python
- Курс «Python для веб-разработки»
- Профессия Frontend-разработчик
- Профессия Веб-разработчик
Мобильная разработка
- Профессия iOS-разработчик
- Профессия Android-разработчик
Java и C#
- Профессия Java-разработчик
- Профессия QA-инженер на JAVA
- Профессия C#-разработчик
- Профессия Разработчик игр на Unity
От основ — в глубину
- Курс «Алгоритмы и структуры данных»
- Профессия C++-разработчик
- Профессия «Белый хакер»
А также
Include algorithm c что это
Определяет функции шаблона контейнера стандартной библиотеки C++, которые выполняют алгоритмы.
Синтаксис
(see links below for specific algorithm syntax)
Библиотека также использует инструкцию #include .
Замечания
Алгоритмы стандартной библиотеки C++ могут работать с различными структурами данных. Структуры данных, с которыми они могут работать, включают не только классы контейнеров стандартной библиотеки C++, такие как vector и , но и list пользовательские структуры данных и массивы элементов, если они удовлетворяют требованиям определенного алгоритма. Алгоритмы стандартной библиотеки C++ достигают такого уровня универсальности путем получения доступа к элементам контейнера и их просмотра опосредованным образом через итераторы.
Алгоритмы стандартной библиотеки C++ обрабатывают диапазоны итератора, которые обычно определяются их начальными или конечными позициями. Диапазоны, упомянутые ниже, должны быть допустимыми в том смысле, что все итераторы в диапазонах должны быть разыменовываемыми и в пределах последовательностей каждого диапазона, последняя позиция должна быть достигнута с первого, добавив итератор.
Алгоритмы стандартной библиотеки C++ могут работать с различными типами объектов контейнеров одновременно. Два суффикса использовались для передачи информации о назначении алгоритмов:
- Суффикс _if указывает, что алгоритм используется с объектами-функциями, которые работают над значениями элементов, а не на самих элементах. Например, find_if алгоритм ищет элементы, значения которых удовлетворяют критерию, заданному объектом-функцией, а find алгоритм выполняет поиск определенного значения.
- Суффикс _copy указывает, что алгоритм обычно изменяет скопированные значения, а не копирует измененные значения. Другими словами, они не изменяют элементы исходного диапазона, но помещают результаты в выходной диапазон или итератор. Например, reverse алгоритм изменяет порядок элементов в диапазоне, а reverse_copy алгоритм копирует обратный результат в диапазон назначения.
Алгоритмы стандартной библиотеки C++ часто классифицируются в группы, чтобы указать их назначение или требования. К ним относятся изменения алгоритмов, которые изменяют значение элементов по сравнению с не изменяющимися алгоритмами, которые не изменяются. Изменяющие алгоритмы меняют порядок элементов, но не значения элементов. Удаляющие алгоритмы могут исключать элементы из диапазона или копии диапазона. Алгоритмы сортировки переупорядочения элементов в диапазоне различными способами и отсортированные алгоритмы диапазона действуют только для диапазонов, элементы которых были отсортированы определенным образом.
Алгоритмы
| Имя | Описание |
|---|---|
| adjacent_find | Поиск двух соседних элементов, которые либо равны, либо удовлетворяют указанному условию. |
| all_of | Возвращает значение true , если условие выполняется каждым элементом заданного диапазона. |
| any_of | Возвращает значение true , если условие выполняется хотя бы один раз в указанном диапазоне элементов. |
| binary_search | Проверяет, есть ли в отсортированном диапазоне элемент, равный указанному значению или эквивалентный ему в смысле, заданном двоичным предикатом. |
| clamp | |
| copy | Присваивает значения элементов из исходного диапазона диапазону назначения, выполняя итерации в исходной последовательности элементов и присваивая им новые позиции в прямом направлении. |
| copy_backward | Присваивает значения элементов из исходного диапазона диапазону назначения, выполняя итерации в исходной последовательности элементов и присваивая им новые позиции в обратном направлении. |
| copy_if | Копирует все элементы в заданном диапазоне, возвращающие true для заданного условия |
| copy_n | Копирует указанное количество элементов. |
| count | Возвращает количество элементов в диапазоне, значения которых соответствуют заданному значению. |
| count_if | Возвращает количество элементов в диапазоне, значения которых соответствуют заданному условию. |
| equal | Сравнивает два диапазона поэлементно либо на признак равенства или равноценности в смысле, заданном бинарным предикатом. |
| equal_range | Находит пару позиций в упорядоченном диапазоне; первая из них меньше или равна позиции указанного элемента, а вторая — больше позиции элемента, где суть равноценности или порядка, используемая, чтобы установить позиции в последовательности, может быть задана бинарным предикатом. |
| fill | Присваивает одно и то же новое значение каждому элементу в заданном диапазоне. |
| fill_n | Присваивает новое значение указанному количеству элементов в диапазоне, начиная с определенного элемента. |
| find | Находит позицию первого вхождения элемента с заданным значением в диапазон. |
| find_end | Ищет в диапазоне последнюю подпоследовательность, совпадающую с заданной последовательностью, или эквивалентной согласно условию, заданному двоичным предикатом. |
| find_first_of | Выполняет поиск первого вхождения любого из нескольких значений в заданный диапазон или первого вхождения любого из нескольких элементов, равноценных в смысле, заданном бинарным предикатом, в указанный набор элементов. |
| find_if | Находит позицию первого вхождения элемента, удовлетворяющего определенному условию, в диапазон. |
| find_if_not | Возвращает первый элемент в указанном диапазоне, который не удовлетворяет условию. |
| for_each | Применяет заданный объект функции к каждому элементу в прямом порядке в пределах диапазона и возвращает объект функции. |
| for_each_n | |
| generate | Присваивает значения, создаваемые объектом функции, каждому элементу в диапазоне. |
| generate_n | Присваивает значения, создаваемые объектом функции, указанному количеству элементов в диапазоне и возвращается на позицию, следующую за последним присвоенным значением. |
| includes | Проверяет, содержит ли один отсортированный диапазон все элементы, содержащиеся во втором отсортированном диапазоне, где порядок сортировки или критерий эквивалентности элементов можно задать бинарным предикатом. |
| inplace_merge | Объединяет элементы из двух последовательных упорядоченных диапазонов в один упорядоченный диапазон, где критерий порядка сортировки может быть указан бинарным предикатом. |
| is_heap | Возвращает значение true , если элементы в указанном диапазоне образуют кучу. |
| is_heap_until | Возвращает значение true , если указанный диапазон образует кучу до последнего элемента. |
| is_partitioned | Возвращает значение true , если все элементы в заданном диапазоне, возвращающие true для какого-либо условия, расположены перед всеми элементами, возвращающими false . |
| is_permutation | Определяет, образуют ли элементы в заданном диапазоне допустимую перестановку. |
| is_sorted | Возвращает значение true , если элементы в указанном диапазоне расположены в порядке сортировки. |
| is_sorted_until | Возвращает значение true , если элементы в указанном диапазоне расположены в порядке сортировки. |
| iter_swap | Меняет местами два значения, указанные парой определенных итераторов. |
| lexicographical_compare | Сравнивает две последовательности поэлементно для определения того, какой элемент из двух меньше. |
| lower_bound | Находит позицию первого элемента в упорядоченном диапазоне, значение которого больше или равно указанному значению, где критерий упорядочивания может быть задан бинарным предикатом. |
| make_heap | Преобразует элементы из указанного диапазона в кучу, в которой первый элемент является наибольшим и для которой критерий сортировки может быть определен бинарным предикатом. |
| max | Сравнивает два объекта и возвращает больший из них, где критерий упорядочивания может быть указан бинарным предикатом. |
| max_element | Находит первое вхождение наибольшего элемента в указанном диапазоне, где критерий упорядочивания может быть указан бинарным предикатом. |
| merge | Объединяет все элементы из двух исходных упорядоченных диапазонов в один упорядоченный диапазон назначения, где критерий порядка сортировки может быть указан бинарным предикатом. |
| min | Сравнивает два объекта и возвращает меньший из них, где критерий упорядочивания может быть указан бинарным предикатом. |
| min_element | Находит первое вхождение наименьшего элемента в указанном диапазоне, где критерий упорядочивания может быть указан бинарным предикатом. |
| minmax | Сравнивает два входных параметра и возвращает их в виде пары, в порядке от меньшего к большему. |
| minmax_element | Выполняет работу, которую делают min_element и max_element , в одном вызове. |
| mismatch | Сравнивает два диапазона поэлементно либо на равенство или равноценность в смысле, заданном бинарным предикатом, и находит первую позицию, где наблюдается разница. |
| move | Перемещает элементы, связанные с заданным диапазоном. |
| move_backward | Перемещает элементы одного итератора в другой. Перемещение начинается с последнего элементом в указанном диапазоне и завершается первым элементом в этом диапазоне. |
| next_permutation | Изменяет порядок элементов в диапазоне, чтобы исходный порядок был заменен перестановкой "лексикографически следующий больший", если такая существует, где смысл термина "следующий" может быть задан бинарным предикатом. |
| none_of | Возвращает значение true , если условие не выполняется ни одним элементом заданного диапазона. |
| nth_element | Разделяет диапазон элементов, правильно находя n-й элемент последовательности в диапазоне, чтобы все элементы перед ним были меньше или равны ему, а все элементы в последовательности после него больше либо равны ему. |
| partial_sort | Упорядочивает указанное число меньших элементов в диапазоне в не нисходящий порядок или согласно критерию упорядочивания, заданному бинарным предикатом. |
| partial_sort_copy | Копирует элементы из исходного диапазона в диапазон назначения, где исходные элементы упорядочены по критерию "меньше либо равно" или согласно другому заданному бинарному предикату. |
| partition | Разделяет элементы диапазона на два непересекающихся множества, при этом элементы, удовлетворяющие унарному предикату, расположены перед теми, которые ему не удовлетворяют. |
| partition_copy | Копирует элементы, возвращающие true для какого-либо условия, в одно место назначения, а возвращающие false — в другое. Эти элементы должны поступать из указанного диапазона. |
| partition_point | Возвращает первый элемент в заданном диапазоне, который не удовлетворяет условию. Элементы сортируются таким образом, чтобы те, которые удовлетворяют условию, приходят до тех, которые не имеют. |
| pop_heap | Удаляет наибольший элемент из начала кучи до позиции, следующей за последней, в диапазоне, а затем формирует новую кучу из оставшихся элементов. |
| prev_permutation | Изменяет порядок элементов в диапазоне, чтобы исходный порядок был заменен перестановкой "лексикографически следующий больший", если такая существует, где смысл термина "следующий" может быть задан бинарным предикатом. |
| push_heap | Добавляет элемент, находящийся в конце диапазона, в существующую кучу, состоящую из предыдущих элементов диапазона. |
| random_shuffle | Выполняет переупорядочивание последовательности N элементов в диапазоне в одном из N! возможных порядков, выбранном случайным образом. |
| remove | Удаляет указанное значение из заданного диапазона без нарушения порядка остальных элементов и возвращает конец нового диапазона после удаления указанного значения. |
| remove_copy | Копирует элементы из исходного диапазона в диапазон назначения, за исключением того, что элементы указанного значения не копируются, не нарушая порядок оставшихся элементов и возвращая конец нового диапазона назначения. |
| remove_copy_if | Копирует элементы из исходного диапазона в диапазон назначения, за исключением того, что выполнение предиката не копируется, не нарушая порядок оставшихся элементов и возвращая конец нового диапазона назначения. |
| remove_if | Удаляет элементы, соответствующие предикату, из заданного диапазона без нарушения порядка остальных элементов и возвращает конец нового диапазона после удаления указанного значения. |
| replace | Проверяет каждый элемент в диапазоне и заменяет его, если он соответствует заданному значению. |
| replace_copy | Проверяет каждый элемент в исходном диапазоне и заменяет его, если он соответствует заданному значению, одновременно копируя результат в новый диапазон назначения. |
| replace_copy_if | Проверяет каждый элемент в исходном диапазоне и заменяет его, если он соответствует заданному предикату, одновременно копируя результат в новый диапазон назначения. |
| replace_if | Проверяет каждый элемент в диапазоне и заменяет его, если он соответствует заданному предикату. |
| reverse | Изменяет порядок элементов в диапазоне на обратный. |
| reverse_copy | Изменяет порядок элементов в исходном диапазоне на обратный, одновременно копируя их в диапазон назначения |
| rotate | Меняет местами элементы в двух соседних диапазонах. |
| rotate_copy | Меняет местами элементы в двух соседних диапазонах в пределах исходного диапазона и копирует результат в диапазон назначения. |
| sample | |
| search | Выполняет поиск первого вхождения последовательности в целевой диапазон, элементы которого равны указанным в заданной последовательности элементов или элементы которого равноценны в смысле, заданным бинарным предикатом, элементам в заданной последовательности. |
| search_n | Выполняет поиск первой подпоследовательности в диапазоне заданного числа элементов, имеющих определенное значение или связанных с этим значением отношением, указанным бинарным предикатом. |
| set_difference | Объединяет все элементы, принадлежащие одному отсортированному исходному диапазону, но не второму отсортированному исходному диапазону, в один отсортированный диапазон назначения, где критерий упорядочивания может быть указан бинарным предикатом. |
| set_intersection | Объединяет все элементы, входящие в оба исходных упорядоченных диапазона, в один упорядоченный диапазон назначения, где критерий порядка сортировки может быть указан бинарным предикатом. |
| set_symmetric_difference | Объединяет все элементы, входящие в один, но не в оба исходных упорядоченных диапазона, в один упорядоченный диапазон назначения, где критерий порядка сортировки может быть указан бинарным предикатом. |
| set_union | Объединяет все элементы, входящие в хотя бы один из двух исходных упорядоченных диапазонов, в один упорядоченный диапазон назначения, где критерий порядка сортировки может быть указан бинарным предикатом. |
| sort | Упорядочивает элементы в указанном диапазоне в не нисходящем порядке или согласно критерию упорядочивания, заданному бинарным предикатом. |
| shuffle | Перемешивает (изменяет порядок) элементы в указанном диапазоне, используя генератор случайных чисел. |
| sort_heap | Преобразует кучу в упорядоченный диапазон. |
| stable_partition | Разделяет элементы диапазона на два непересекающихся множества, при этом элементы, удовлетворяющие унарному предикату, расположены перед теми, которые ему не удовлетворяют, с сохранением относительного порядка равноценных элементов. |
| stable_sort | Упорядочивает элементы в указанном диапазоне в не нисходящем порядке или согласно критерию упорядочивания, заданному бинарным предикатом, и сохраняет относительный порядок равноценных элементов. |
| swap | Меняет местами значения элементов между двумя типами объектов, присваивая содержимое первого объекта второму объекту, а содержимое второго — первому. |
| swap_ranges | Меняет местами элементы одного диапазона с элементами другого диапазона такого же размера. |
| transform | Применяет заданный объект функции к каждому элементу в исходном диапазоне или к паре элементов из двух исходных диапазонов и копирует возвращаемые значения объекта функции в диапазон назначения. |
| unique | Удаляет повторяющиеся элементы, которые находятся рядом друг с другом в указанном диапазоне. |
| unique_copy | Копирует элементы из исходного диапазона в диапазон назначения, за исключением повторяющихся элементов, которые находятся рядом друг с другом. |
| upper_bound | Находит позицию первого элемента в упорядоченном диапазоне, который имеет значение больше указанного значения, где критерий упорядочивания может быть задан бинарным предикатом. |
Алгоритмы диапазонов C++20 — сортировка, множества, обновления C++23 и прочее

Эта статья — третья и последняя в мини-серии об алгоритмах диапазонов. Мы рассмотрим некоторые алгоритмы сортировки, поиска и другие, а также познакомимся с готовящимися крутыми улучшениями этих алгоритмов в версии C++23. Поехали! Подробности — к старту курса по разработке на С++.
Прежде чем мы начнём
Ключевые наблюдения для алгоритмов std::ranges :
- Алгоритмы диапазонов определяются в заголовке , а инфраструктура диапазонов и основные типы определяются в заголовке .
- Существует как минимум две перегрузки алгоритмов диапазона: с парой итераторов и перегрузка с одним аргументом диапазона.
- Версия, которая возвращает поддиапазон или итератор и принимает диапазон, возвращает заимствованный диапазон или заимствованный итератор. Это помогает обнаруживать итераторы для временных диапазонов.
- Версии диапазона имеют проекции, что обеспечивает большую гибкость; например, вы можете выполнить сортировку по некоторым выбранным элементам или выполнить дополнительные преобразования перед сравнением.
- В версии с диапазонами нет опции параллельного выполнения (вы не можете передать политику std::execution ).
- Алгоритмы диапазонов, как и стандартные алгоритмы C++20, также являются constexpr .
- Начиная с версии C++20, не существует алгоритмов числовых диапазонов, соответствующих заголовку .
Ниже вы можете найти примеры, демонстрирующие стандартный алгоритм и альтернативную версию с диапазонами. В них мы иллюстрируем некоторые основные концепции, стараясь не использовать расширенную композицию диапазонов или представления. Мы пойдём в порядке, указанном в cppreference/algorithms.
В этой части рассмотрим алгоритмы сортировки, разбиения на разделы, бинарный поиск и некоторые другие функции.
- первая статья: «Алгоритмы диапазонов C++20 — 7 немодифицирующих операций»;
- вторая статья: «Алгоритмы диапазонов C++20 — 11 модифицирующих операций»;
Разбиение на разделы и сортировка
sort и is_sorted
Алгоритм сортировки часто используют для объявления диапазонов. Если у вас есть контейнер, то благодаря диапазонам вы можете написать:
std::ranges::sort(myContainer);
Вот пример для лучшего понимания:
#include #include #include #include struct Product < std::string name; double value < 0.0 >; >; void print(std::string_view intro, const std::vector& container) < std::cout int main() < const std::vectorprods < < "box", 10.0 >, , , < "car", 1000.0 >, , , < "book", 45.0>, , >; print("input", prods); // the standard version: std::vector copy = prods; std::sort(begin(copy), end(copy), [](const Product& a, const Product& b) < return a.name < b.name; >); print("after sorting by name", copy); // the ranges version: copy = prods; std::ranges::sort(copy, <>, &Product::name); print("after sorting by name", copy); std::ranges::sort(copy, <>, &Product::value); print("after sorting by value", copy); auto sorted = std::ranges::is_sorted(copy, <>, &Product::value); std::cout
Во многих реализациях используется интросортировка (см. Википедию). Это гибридное решение, обычно с быстрой сортировкой/сортировкой кучей, а затем сортировкой вставками для небольших (под)диапазонов.
Другие версии алгоритмов сортировки:
- partial_sort — сортирует первые N элементов диапазона.
- stable_sort — порядок эквивалентных элементов гарантированно сохраняется.
Как видите, в версии с диапазонами легко передать проекцию и отсортировать по заданной части элемента. В обычной версии нужно отдельное лямбда-выражение…
partition
Разбиение — неотъемлемая часть быстрой сортировки. Для данного предиката операция перемещает элементы, соответствующие предикату, в первую часть контейнера, а не соответствующие — во вторую. Иногда можно разделить контейнер, а не выполнять полную операцию сортировки:
#include #include #include #include void print(std::string_view intro, const std::vector& container) < std::cout int main() < const std::vector vec < 11, 2, 3, 9, 5, 4, 3, 8, 4, 1, 11, 12, 10, 4>; print("input", vec); // the standard version: auto copy = vec; auto it = std::partition(begin(copy), end(copy), [](int a) < return a < 7; >); print("partition till 7", copy); std::cout ); print("partition till 7", copy); std::cout
input 11, 2, 3, 9, 5, 4, 3, 8, 4, 1, 11, 12, 10, 4, partition till 7 4, 2, 3, 1, 5, 4, 3, 4, 8, 9, 11, 12, 10, 11, pivot at 8 partition till 7 4, 2, 3, 1, 5, 4, 3, 4, 8, 9, 11, 12, 10, 11, pivot at 8
Как видите, мы легко можем разделить контейнер на две группы: первая часть содержит элементы меньше 7, а вторая часть — элементы >= 7 . Относительный порядок между элементами может быть изменен (для сохранения этого порядка нужно использовать stable_partition ).
Интерфейс для partition относительно прост. Версия алгоритма дополнительно принимает проекцию, но в примере она не использовалась. Отличие состоит в том, что ranges::partition возвращает поддиапазон, а не итератор (как в версии std:: ).
Операции бинарного поиска
Если контейнер уже отсортирован, можно выполнять операции логарифмического бинарного поиска.
binary_search
#include #include #include #include #include void print(std::string_view intro, const auto& container) < std::cout int main() < std::vectorvec(100, 0); std::iota(begin(vec), end(vec), 0); print("first ten elements of input", vec | std::views::take(10)); // the standard version: auto copy = vec; auto found = std::binary_search(begin(copy), end(copy), 13); std::cout
Кроме того, можно использовать связанные алгоритмы:
- std::ranges::lower_bound — cppreference.com возвращает итератор к первому элементу не меньше заданного значения;
- std::ranges::upper_bound — cppreference.com — возвращает итератор к первому элементу больше заданного значения;
Операции с множествами
В библиотеке есть много функций, связанных с множествами, вот некоторые из них:
- ranges::merge — объединяет два отсортированных диапазона
- ranges::inplace_merge — объединяет два упорядоченных диапазона in-place
- ranges::includes — возвращает true, если одна отсортированная последовательность является подпоследовательностью другой отсортированной последовательности
- ranges::set_difference — вычисляет разницу между двумя множествами
- ranges::set_intersection — вычисляет пересечение двух множеств
- ranges::set_symmetric_difference — вычисляет симметричную разность двух множеств
- ranges::set_union — вычисляет объединение двух множеств
Рассмотрим один пример с includes :
includes
Возвращает true , если отсортированный диапазон является подпоследовательностью другого отсортированного диапазона.
#include #include #include #include #include struct Product < std::string name; double value < 0.0 >; >; void print(std::string_view intro, const std::vector& container) < std::cout int main() < std::vectorprods < < "box", 10.0 >, , , < "car", 1000.0 >, , , < "book", 45.0>, , >; std::vector vecToCheck < , < "box", 10.0 >, >; std::ranges::sort(prods, <>, &Product::name); std::vector namesToCheck ; print("input", prods); // the standard version: auto ret = std::includes(begin(prods), end(prods), begin(vecToCheck), end(vecToCheck), [](const Product& a, const Product& b) < return a.name < b.name; >); std::cout , &Product::name); std::cout
Версия с диапазонами проще и предлагает способ проверки различных контейнеров. При подходе std:: итератор необходимо разыменовать, а затем неявно преобразовать в оба типа элементов входного контейнера.
Прочее
max_element
Поиск наибольшего элемента в контейнере (без сортировки):
#include #include #include #include #include struct Product < std::string name_; double value_ < 0.0 >; >; int main() < const std::vectorprods < < "box", 10.0 >, , , < "car", 1000.0 >, , , < "book", 45.0>, , >; // the standard version: auto res = std::max_element(begin(prods), end(prods), [](const Product& a, const Product& b) < return a.value_ < b.value_; >); if (res != end(prods)) < const auto pos = std::distance(begin(prods), res); std::cout value_ // the ranges version: auto it = std::ranges::max_element(prods, <>, &Product::value_); if (it != end(prods)) < const auto pos = std::distance(begin(prods), it); std::cout value_ >
equal
#include #include #include #include #include struct Product < std::string name; double value < 0.0 >; >; int main() < const std::vectorprods < < "box", 10.0 >, , , < "car", 1000.0 >, , , >; const std::vector moreProds < < "box", 11.0 >, , , < "car", 10.0 >, , >; // the standard version: auto res = std::equal(begin(prods), end(prods), begin(moreProds), end(moreProds), [](const Product& a, const Product& b) < return a.name == b.name; >); std::cout , &Product::name, &Product::name); std::cout
Ещё больше
Мой список алгоритмов не полон. Почти у всех стандартных алгоритмов есть альтернатива std::ranges:: . Взгляните на следующие интересные, не упомянутые мной алгоритмы:
Операции с кучей:
- ranges::is_heap
- ranges::is_heap_until
- ranges::make_heap
- ranges::push_heap
- ranges::pop_heap
- ranges::sort_heap
- ranges::is_permutation
- ranges::next_permutation
- ranges::prev_permutation
Алгоритмы неинициализированной памяти:
- ranges::uninitialized_copy
- ranges::uninitialized_copy_n
- ranges::uninitialized_fill
- ranges::uninitialized_fill_n
- ranges::uninitialized_move
- ranges::uninitialized_move_n
- ranges::uninitialized_default_construct
- ranges::uninitialized_default_construct_n
- ranges::uninitialized_value_construct
- ranges::uninitialized_value_construct_n
- ranges::destroy
- ranges::destroy_n
- ranges::destroy_at
- ranges::construct_at
Numeric
С версии C++20 у нас есть большинство соответствующих алгоритмов диапазонов из заголовка , но отсутствует заголовок .
Скоро в C++23
Спецификация C++23 почти закончена и находится в режиме feature-freeze. На момент написания статьи мне известны следующие алгоритмы, которые появятся в новой версии C++:
- ranges::starts_with и ranges::ends_with (по состоянию на июнь 2022 г. доступно в компиляторе MSVC)
- ranges::contains (P2302)
- ranges::shift_left и ranges::shift_right ,
- ranges::iota
- ranges::fold в качестве альтернативы std::accumulate
Заключение
Эта статья завершает наше путешествие по алгоритмам C++, доступным в стандартной библиотеке (кроме числовых). У большинства алгоритмов есть аналоги ranges:: , а в C++23 появится ещё больше дополнений.
А мы научим вас аккуратно работать с данными, чтобы вы прокачали карьеру и стали востребованным IT-специалистом.

- Профессия Fullstack-разработчик на Python (16 месяцев)
- Профессия Data Scientist (24 месяца)
Краткий каталог курсов
Data Science и Machine Learning
- Профессия Data Scientist
- Профессия Data Analyst
- Курс «Математика для Data Science»
- Курс «Математика и Machine Learning для Data Science»
- Курс по Data Engineering
- Курс «Machine Learning и Deep Learning»
- Курс по Machine Learning
Python, веб-разработка
- Профессия Fullstack-разработчик на Python
- Курс «Python для веб-разработки»
- Профессия Frontend-разработчик
- Профессия Веб-разработчик
Мобильная разработка
- Профессия iOS-разработчик
- Профессия Android-разработчик
Java и C#
- Профессия Java-разработчик
- Профессия QA-инженер на JAVA
- Профессия C#-разработчик
- Профессия Разработчик игр на Unity
От основ — в глубину
- Курс «Алгоритмы и структуры данных»
- Профессия C++-разработчик
- Профессия «Белый хакер»
А также