Application of STL containers and algorithms in C++
Hello, Habre!
STL – this collection componentsintended for working with data. Includes: containers, algorithms, iterators and functional objects, STL is generally a kind of Swiss army knife in this matter. Containers help to manage collections of data of various types, algorithms provide general methods of their processing, iterators serve as a connecting link between containers and algorithms, and functional objects allow you to flexibly configure the behavior of standard operations.
Let’s consider in more detail.
Contents
STL containers
Sequential containers
std::vector
is a dynamic array that can change its size during program execution, it allows quick access to elements by index, making it one of the most commonly used containers in C++:
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
numbers.push_back(6); // добавляем элемент в конец
// доступ к элементам
std::cout << "Third element: " << numbers[2] << std::endl;
// итерация по vector
for(int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
std::list
is a doubly linked list that allows you to insert and remove elements from any position in the container. Unlike vector
, list
does not support direct access by index, but generally more flexible:
#include <iostream>
#include <list>
int main() {
std::list<int> numbers = {1, 2, 3, 4, 5};
numbers.push_front(0); // добавляем элемент в начало списка
// удаление элемента
numbers.remove(3);
// итерация по list
for(int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
std::deque
(double-ended queue) is an indexed sequence of elements that supports addition and deletion from both the beginning and the end of the container. deque
combines some of the qualities vector
and list
:
#include <iostream>
#include <deque>
int main() {
std::deque<int> numbers = {2, 3, 4};
numbers.push_front(1); // добавляем элемент в начало
numbers.push_back(5); // дбавляем элемент в конец
// доступ к элементам
std::cout << "First element: " << numbers.front() << std::endl;
std::cout << "Last element: " << numbers.back() << std::endl;
// удаление первого элемента
numbers.pop_front();
// итерация по deque
for(int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
Associative containers
std::set
– This is a container that stores unique items in a sorted order. Suitable for tasks where you need to quickly check the presence of elements without worrying about duplicates:
#include <iostream>
#include <set>
int main() {
std::set<int> numbers = {4, 2, 1, 3, 5};
numbers.insert(6); // добавляем новый элемент
numbers.insert(3); // попытка добавить дубликат без эффекта т.е
// проверяем наличие элемента
if (numbers.find(4) != numbers.end()) {
std::cout << "4 is in the set" << std::endl;
}
// итерация по set
for(int num : numbers) {
std::cout << num << " "; // элементы будут отсортированы
}
std::cout << std::endl;
return 0;
}
std::multiset
similar to set
but allows you to store duplicate elements:
#include <iostream>
#include <set>
int main() {
std::multiset<int> numbers = {5, 2, 1, 3, 2};
numbers.insert(2); // добавляем еще один дубликат
// Итерация по multiset
for(int num : numbers) {
std::cout << num << " "; // дубликаты сохранены
}
std::cout << std::endl;
return 0;
}
std::map
is an associative container that stores key-value pairs in sorted order. Each key in map
is unique and can be used to quickly access the corresponding value:
#include <iostream>
#include <map>
int main() {
std::map<std::string, int> fruitCount = {{"apple", 2}, {"banana", 5}};
fruitCount["orange"] = 4; // Добавляем новую пару ключ-значение
// доступ к элементам
std::cout << "Apples: " << fruitCount["apple"] << std::endl;
// итерация по map
for(const auto& pair : fruitCount) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
std::multimap
works similarly map
but allows you to store multiple values of the same key:
#include <iostream>
#include <map>
int main() {
std::multimap<std::string, int> fruitCount = {{"apple", 2}, {"apple", 3}, {"banana", 5}};
fruitCount.insert({"apple", 1}); // добавляем еще одно значение для ключа "apple"
// итерация по multimap
auto range = fruitCount.equal_range("apple");
for(auto it = range.first; it != range.second; ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
return 0;
}
Unordered containers
std::unordered_set
– This is a container that stores unique elements in an unordered form. It provides very fast insertion, deletion and search operations by using a hash table:
#include <iostream>
#include <unordered_set>
int main() {
std::unordered_set<int> numbers = {4, 1, 2, 3, 5};
numbers.insert(6); // добавляем новый элемент
numbers.erase(1); // удаляем элемент
// проверяем наличие элемента
if (numbers.find(4) != numbers.end()) {
std::cout << "4 is in the set" << std::endl;
}
// итерация по unordered_set
for(int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
std::unordered_map
is an associative container that stores key-value pairs in an unordered form:
#include <iostream>
#include <unordered_map>
int main() {
std::unordered_map<std::string, int> fruitCount = {{"apple", 2}, {"banana", 5}};
fruitCount["orange"] = 4; // дбавляем новую пару ключ-значение
// удаление элемента
fruitCount.erase("banana");
// доступ к элементам
std::cout << "Apples: " << fruitCount["apple"] << std::endl;
// итерация по unordered_map
for(const auto& pair : fruitCount) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
Container adapters
std::stack
provides stack functionality by implementing LIFO . That is, this means that the last element added will be the first when removed:
#include <iostream>
#include <stack>
int main() {
std::stack<int> numbers;
numbers.push(1); // добавляем элементы
numbers.push(2);
numbers.push(3);
// Извлекаем элементы
while (!numbers.empty()) {
std::cout << numbers.top() << " "; // показываем верхний элемент
numbers.pop(); // удаляем верхний элемент
}
std::cout << std::endl;
return 0;
}
std::queue
implements the queue functionality by following FIFO:
#include <iostream>
#include <queue>
int main() {
std::queue<int> numbers;
numbers.push(1); // добавляем элементы
numbers.push(2);
numbers.push(3);
// извлекаем элементы
while (!numbers.empty()) {
std::cout << numbers.front() << " "; // показываем первый элемент
numbers.pop(); // удаляем первый элемент
}
std::cout << std::endl;
return 0;
}
std::priority_queue
manipulates a set of elements so that the highest (or priority) element is always on top:
#include <iostream>
#include <queue>
int main() {
std::priority_queue<int> numbers;
numbers.push(3);
numbers.push(1);
numbers.push(4);
numbers.push(2);
// иззвлекаем элементы
while (!numbers.empty()) {
std::cout << numbers.top() << " "; // показываем элемент с наивысшим приоритетом
numbers.pop(); // удаляем элемент с наивысшим приоритетом
}
std::cout << std::endl;
return 0;
}
STL algorithms
Search
Search algorithms in STL help find elements in containers. One of the most basic — std::find
:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
auto it = std::find(data.begin(), data.end(), 3);
if (it != data.end()) {
std::cout << "Found: " << *it << std::endl;
} else {
std::cout << "Not found" << std::endl;
}
return 0;
}
Sorting
Sorting is also a basic operation in programming. STL offers std::sort
to sort:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {5, 1, 4, 2, 3};
std::sort(data.begin(), data.end());
for (int n : data) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Permutation
STL offers to rearrange elements std::rotate
and std::next_permutation
:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
std::rotate(data.begin(), data.begin() + 2, data.end());
for (int n : data) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Copying
std::copy
allows you to copy elements from one container to another:
#include <algorithm>
#include <vector>
#include <iostream>
#include <iterator>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dest(5);
std::copy(src.begin(), src.end(), dest.begin());
for (int n : dest) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Modification
There are also algorithms for modifying elements, for example std::transform
:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
std::transform(data.begin(), data.end(), data.begin(),
[](int x) { return x * x; });
for (int n : data) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Deleting items
std::remove
and std::remove_if
allows you to remove elements from the container under the condition:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6};
auto newEnd = std::remove_if(data.begin(), data.end(),
[](int x) { return x % 2 == 0; });
data.erase(newEnd, data.end());
for (int n : data) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Several application examples
Suppose we have a list of employees and we want to first group them by department and then sort within each group by descending salary:
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
struct Employee {
std::string name;
std::string department;
int salary;
// конструктор для удобства
Employee(std::string n, std::string d, int s) : name(n), department(d), salary(s) {}
};
// функция для вывода списка сотрудников
void printEmployees(const std::vector<Employee>& employees) {
for (const auto& e : employees) {
std::cout << e.name << " from " << e.department << ", Salary: " << e.salary << std::endl;
}
}
int main() {
std::vector<Employee> employees = {
{"Alice", "Accounting", 50000},
{"Bob", "HR", 45000},
{"Charlie", "IT", 70000},
{"Diana", "Accounting", 55000},
{"Eve", "HR", 48000},
{"Frank", "IT", 52000}
};
// сначала группируем по отделам
std::stable_sort(employees.begin(), employees.end(),
[](const Employee& a, const Employee& b) {
return a.department < b.department;
});
// затем сортируем внутри каждой группы по убыванию зарплаты
std::stable_sort(employees.begin(), employees.end(),
[](const Employee& a, const Employee& b) {
return a.salary > b.salary;
});
printEmployees(employees);
return 0;
}
Sometimes it is necessary to remove all duplicates from a vector, keeping only unique elements:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 4, 2, 3, 2, 3, 4, 5, 5, 6};
// сортируем данные
std::sort(data.begin(), data.end());
// удаляем дубликаты
auto last = std::unique(data.begin(), data.end());
data.erase(last, data.end());
for (int n : data) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Let’s say we have a vector of integers and we want to transform each element of the vector by multiplying it by 2 and subtracting 1. This can be done with std::transform
:
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
std::vector<int> transformedData(data.size());
std::transform(data.begin(), data.end(), transformedData.begin(),
[](int x) { return x * 2 - 1; });
for (int n : transformedData) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
A bit about STL iterators
Input iterators – This is the most basic type of iterators that supports reading sequential access. Can be used for one-pass reading of data from the container:
#include <iostream>
#include <vector>
#include <iterator>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
std::istream_iterator<int> start(std::cin), end; // чтение данных из стандартного ввода
std::vector<int> numbers(start, end); // инициализация вектора считанными числами
for (int n : numbers) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Output iterators intended for one-pass recording of data into the container:
#include <iostream>
#include <iterator>
#include <algorithm>
int main() {
std::ostream_iterator<int> out_it (std::cout,", ");
std::vector<int> data = {1, 2, 3, 4, 5};
std::copy(data.begin(), data.end(), out_it); // вывод данных в стандартный вывод
return 0;
}
Forward iterators support reading and writing with the possibility of multiple passes through elements:
#include <forward_list>
#include <iostream>
int main() {
std::forward_list<int> flist = {1, 2, 3, 4, 5};
for (auto it = flist.begin(); it != flist.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
Bidirectional iterators extend the capabilities of forward iterators by adding the possibility of bidirectional traversal (forward and backward):
#include <list>
#include <iostream>
int main() {
std::list<int> l = {1, 2, 3, 4, 5};
for (auto it = l.rbegin(); it != l.rend(); ++it) { // обратный обход
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
Random access iterators provide all the capabilities of bidirectional iterators, and also support direct access to any element in constant time:
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::cout << "Third element: " << v[2] << std::endl; // прямой доступ
std::cout << "Second element using iterator: " << *(v.begin() + 1) << std::endl; // Доступ через итератор
return 0;
}
Let’s summarize briefly: STL in C++ allows you to speed up development and improve code quality.
Taking this opportunity, I would like to remind you about the free webinar that will be held tonight as part of the OTUS course on C++ development. In this lesson, participants will analyze dynamic memory allocation during the compilation phase of C++20. Register if applicable.
The full catalog of courses can be found in the catalog.