Skip to content
Open
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
9cfcf59
delete extra extension from docker
UmbrellaLeaf5 Sep 7, 2024
39f8a8f
update format CMakeLists
UmbrellaLeaf5 Sep 7, 2024
aa962ae
update lib folder
UmbrellaLeaf5 Sep 7, 2024
6e59658
update lib->utils name in CMakeLists
UmbrellaLeaf5 Sep 14, 2024
b5d47d1
Merge remote-tracking branch 'upstream/main'
UmbrellaLeaf5 Sep 21, 2024
ae45494
README beautify
UmbrellaLeaf5 Sep 21, 2024
6032727
add graph_additional_task
UmbrellaLeaf5 Sep 27, 2024
bcd54e5
docker file back
UmbrellaLeaf5 Sep 27, 2024
5230994
update edge.Name()
UmbrellaLeaf5 Sep 29, 2024
cf04922
update class Graph with unordered_map as AdjList
UmbrellaLeaf5 Sep 30, 2024
4c939f8
update names and comments
UmbrellaLeaf5 Sep 30, 2024
ff9397f
Merge remote-tracking branch 'upstream/main' into graph_additional_task
UmbrellaLeaf5 Oct 4, 2024
1ccac8e
CMakeLists hotfix
UmbrellaLeaf5 Oct 4, 2024
64008e7
Merge remote-tracking branch 'origin/graph_additional_task' into stro…
UmbrellaLeaf5 Oct 5, 2024
3976ff9
move graph to lib
UmbrellaLeaf5 Oct 5, 2024
04c762f
Merge remote-tracking branch 'upstream/main' into strongly_connected_…
UmbrellaLeaf5 Oct 5, 2024
b85a801
Merge remote-tracking branch 'upstream/main' into graph_additional_task
UmbrellaLeaf5 Oct 5, 2024
ffa9925
and const
UmbrellaLeaf5 Oct 6, 2024
4e8ea35
create folder
UmbrellaLeaf5 Oct 6, 2024
b9d5975
finish primary realization
UmbrellaLeaf5 Oct 7, 2024
c47cb73
fix mistakes
UmbrellaLeaf5 Oct 7, 2024
4fa263b
update README.md and add comments
UmbrellaLeaf5 Oct 7, 2024
8691cba
add tests
UmbrellaLeaf5 Oct 7, 2024
b224e5b
move graph class to lib
UmbrellaLeaf5 Oct 25, 2024
4ddb11c
create folder for dag_relaxation
UmbrellaLeaf5 Oct 25, 2024
6bfef24
add topological_sort from task_01
UmbrellaLeaf5 Oct 6, 2024
ffd686d
primary complete dag_relaxation
UmbrellaLeaf5 Oct 25, 2024
fb1cbba
add more exception throwing
UmbrellaLeaf5 Oct 25, 2024
0919619
code beautify
UmbrellaLeaf5 Oct 25, 2024
f737732
fix typo
UmbrellaLeaf5 Oct 25, 2024
05774f8
fix typo and add ContainsVert method in graph class
UmbrellaLeaf5 Oct 25, 2024
60b581a
fix typo and add ContainsVert method in graph class
UmbrellaLeaf5 Oct 25, 2024
6672c40
add ContainsVert method
UmbrellaLeaf5 Oct 25, 2024
60f10e7
code beautify
UmbrellaLeaf5 Oct 25, 2024
80637ba
add tests
UmbrellaLeaf5 Oct 26, 2024
a530c1f
update README.md
UmbrellaLeaf5 Oct 26, 2024
12dc12a
beautify concepts (move from constructor)
UmbrellaLeaf5 Oct 26, 2024
4000c47
beautify concepts (move from constructor) in graph class
UmbrellaLeaf5 Oct 26, 2024
b553558
beautify concepts (move from constructor) in graph class
UmbrellaLeaf5 Oct 26, 2024
729a22e
Merge remote-tracking branch 'upstream/main' into strongly_connected_…
UmbrellaLeaf5 Oct 27, 2024
664a7e5
Merge remote-tracking branch 'upstream/main' into dag_relaxation_addi…
UmbrellaLeaf5 Oct 27, 2024
cfc14c9
Merge remote-tracking branch 'upstream/main' into graph_additional_task
UmbrellaLeaf5 Oct 27, 2024
f1d3404
code beautify
UmbrellaLeaf5 Oct 27, 2024
6cb4ae5
code beautify
UmbrellaLeaf5 Oct 27, 2024
f01684b
code beautify
UmbrellaLeaf5 Oct 27, 2024
dd17ff5
change static funcs to anonymous namespace
UmbrellaLeaf5 Oct 27, 2024
19cffc6
update CMakeLists.txt
UmbrellaLeaf5 Oct 29, 2024
f4321ff
update CMakeLists.txt
UmbrellaLeaf5 Oct 29, 2024
01b22c8
update CMakeLists.txt
UmbrellaLeaf5 Oct 29, 2024
5dee1f6
change static funcs to anonymous namespace
UmbrellaLeaf5 Oct 30, 2024
650045a
code beautify
UmbrellaLeaf5 Oct 30, 2024
d40c1bd
code beautify
UmbrellaLeaf5 Oct 30, 2024
903be8b
fix tests
UmbrellaLeaf5 Nov 16, 2024
16f71c5
update task using Petya and Vasya scary maze problem
UmbrellaLeaf5 Nov 17, 2024
1f3015b
update tests and README.md
UmbrellaLeaf5 Nov 17, 2024
8cdba58
update task using chemical experiments problem
UmbrellaLeaf5 Nov 18, 2024
e252cdf
move graph class to lib
UmbrellaLeaf5 Oct 25, 2024
2382a0d
Merge branch 'strongly_connected_components_additional_task' into add…
UmbrellaLeaf5 Nov 23, 2024
ce2993f
Merge branch 'dag_relaxation_additional_task' into additional_tasks
UmbrellaLeaf5 Nov 23, 2024
609b741
rename algorithms folders to tasks names
UmbrellaLeaf5 Nov 23, 2024
7a14ed6
rename: start_vert -> start in DAGRelaxation
UmbrellaLeaf5 Nov 25, 2024
380efe7
add AStar primary realization
UmbrellaLeaf5 Nov 25, 2024
c87ebc8
optimize AStar: remove non_visited_verts
UmbrellaLeaf5 Nov 25, 2024
c2b29aa
code beautify
UmbrellaLeaf5 Nov 25, 2024
b7fda02
update comments
UmbrellaLeaf5 Nov 25, 2024
5a1efed
add GoodSolution and its maze parsing
UmbrellaLeaf5 Nov 25, 2024
5d4385a
update tests
UmbrellaLeaf5 Nov 25, 2024
b600b27
update README.md
UmbrellaLeaf5 Nov 25, 2024
a67d809
update comments in graph class and add move constructor
UmbrellaLeaf5 Nov 29, 2024
cbe81d7
update graph class: allow negative weight and add more template types
UmbrellaLeaf5 Nov 29, 2024
dfe0bfe
fix bugs in methods in class graph connected with undirected graph
UmbrellaLeaf5 Nov 29, 2024
e5fa95a
complete GitHub suggestions
UmbrellaLeaf5 Nov 29, 2024
1097fec
code beautify
UmbrellaLeaf5 Nov 29, 2024
fc5f0d2
rename: GetWeightOfEdge -> GetEdgeWeight, ParseEdgeString -> ParseEdg…
UmbrellaLeaf5 Dec 7, 2024
6d560f1
add graph class methods: GetEdgeIter, SetEdgeWeight
UmbrellaLeaf5 Dec 8, 2024
5761e70
move all methods in graph class to graph.hpp
UmbrellaLeaf5 Dec 8, 2024
efa50c7
move graph.hpp and utils.hpp to .. folder
UmbrellaLeaf5 Dec 8, 2024
fc3e316
update graph class (code simplify and beautify)
UmbrellaLeaf5 Dec 9, 2024
6e411db
update graph class (consts and integral->arithmetic)
UmbrellaLeaf5 Jan 13, 2025
07019ff
CMake structure update and code beautify
UmbrellaLeaf5 Jan 14, 2025
8afd94e
complete find_word_chains additional_task
UmbrellaLeaf5 Jan 15, 2025
2db05e3
complete bridge_guards additional_task
UmbrellaLeaf5 Jan 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

project(homeworks)
project(homeworks LANGUAGES CXX)

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic -std=c++23 -O2")

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sandbox)

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/additional_tasks)

file(GLOB_RECURSE tasks_dirs LIST_DIRECTORIES true ".")
file(GLOB_RECURSE SUBFOLDERS LIST_DIRECTORIES true ".")

foreach(dir ${tasks_dirs})
IF(IS_DIRECTORY ${dir})
IF(${dir} MATCHES "task_0[0-9]$" AND NOT ${dir} MATCHES "build")
add_subdirectory(${dir})
ENDIF()
ELSE()
CONTINUE()
ENDIF()
foreach(FOLDER ${SUBFOLDERS})
if(IS_DIRECTORY ${FOLDER})
if(${FOLDER} MATCHES "task_0[0-9]$" AND NOT ${FOLDER} MATCHES "build")
add_subdirectory(${FOLDER})
endif()
endif()
endforeach()
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Домашнее задание для 2 семестра алгоритмов и структур данных
## (Homework for second semester algorithms and data structures on MIPT DAFE/RSE)

### Для удобства можно пользоваться папкой lib, все файлы из этой папки будут подключаться к любой задаче

### Можно получить дополнительные баллы, если добавить интересные текстовые задачи. Необходимы текст задачи, решение и тесты. Каждая задача отдельный ПР, полчуть дополнительные баллы можно только если пулл реквест замержен в основную ветку.
Можно получить дополнительные баллы, если добавить интересные текстовые задачи. Необходимы текст задачи, решение и тесты. Каждая задача отдельный - Pull Request, получить дополнительные баллы можно только если PR замерджен в основную ветку.

### Можно получить дополнительные баллы, если добавить теорию в папку doc. Делается в отдельном ПР, полчуть дополнительные баллы можно только если пулл реквест замержен в основную ветку.
Можно получить дополнительные баллы, если добавить теорию в папку doc. Делается в отдельном PR, только если PR замерджен в основную ветку.

### Код должен быть отформатирован clang-format'ом со стилем Google
Код должен быть отформатирован clang-format'ом со стилем Google.
20 changes: 9 additions & 11 deletions additional_tasks/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

project(additional_tasks)
project(additional_tasks LANGUAGES CXX)

file(GLOB_RECURSE tasks_dirs LIST_DIRECTORIES true ".")
file(GLOB_RECURSE SUBFOLDERS LIST_DIRECTORIES true ".")

foreach(dir ${tasks_dirs})
IF(IS_DIRECTORY ${dir})
IF(NOT ${dir} MATCHES ".*src.*")
add_subdirectory(${dir})
ENDIF()
ELSE()
CONTINUE()
ENDIF()
foreach(FOLDER ${SUBFOLDERS})
if(IS_DIRECTORY ${FOLDER})
if(NOT ${FOLDER} MATCHES ".*src.*")
add_subdirectory(${FOLDER})
endif()
endif()
endforeach()
39 changes: 39 additions & 0 deletions additional_tasks/bridge_guards/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
Utils
)

include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME}_tests)
221 changes: 221 additions & 0 deletions additional_tasks/bridge_guards/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
# Задача: охранники речных мостов

Река имеет `N` контрольно-пропускных пунктов на левом берегу и `M` контрольно-пропускных пунктов на правом берегу.

`P` мостов построены, соединяя контрольно-пропускные пункты через реку. На контрольно-пропускных пунктах необходимо разместить охрану, и охрана может защищать все мосты, на которых присутствует этот КПП.

Для защиты одного моста может быть более одного охранника.

Найдите минимальное количество охранников, необходимое для защиты всех мостов через реку.

## Входные данные:

* Первая строка ввода состоит из 2 целых чисел, разделенных пробелами `N`
и `M` − количество КПП на левом и правом берегу реки соответственно (1 ≤ `N`, `M` ≤ 100)
* Вторая строка ввода состоит из одного целого числа `P` − общее количество мостов через реку (1 ≤ `P` ≤ 100).
* Следующие `P` строк, каждая из которых состоит из 2 целых чисел, разделенных пробелами `u`, `v`, обозначающих, что между КПП `u` на левом берегу и КПП `v` на правом берегу есть мост (1 ≤ `u` ≤ `N`) (1 ≤ `v` ≤ `M`).

## Выходные данные:

* Одно целое число − минимальное количество охранников, необходимое для защиты всех мостов через реку.

## Пример:

### Вход:
```
4 3
4
1 3
1 2
2 2
4 1
```

### Выход:
```
3
```

### Решение:

#### Введение и моделирование графом:

Для решения задачи о часовых на реке, смоделируем ситуацию с помощью двудольного графа.
Одна доля графа представляла пункты наблюдения на левом берегу, а другая – на правом берегу.

Ребра между долями обозначали мосты. Целью было найти минимальный набор вершин (пунктов), которые ‘покрывают’ все ребра (мосты). Это классическая задача поиска минимального вершинного покрытия.

Известно, что в общем случае задача поиска минимального вершинного покрытия является NP-трудной, то есть не существует быстрого алгоритма для произвольных графов. Однако, для двудольных графов эту задачу можно решить за полиномиальное время, применив теорему Кёнига.

**Теорема Кёнига** утверждает, что в двудольном графе размер минимального вершинного покрытия равен размеру максимального паросочетания.

*Паросочетание* – это набор ребер, которые не имеют общих вершин, то есть ни одна вершина не является концом более чем одного ребра в паросочетании.

*Максимальное паросочетание* – это паросочетание с наибольшим возможным количеством ребер.

#### Максимальное паросочетание и алгоритм поиска максимального потока:

Для нахождения максимального паросочетания используем алгоритм поиска максимального потока.

Чтобы применить этот алгоритм, преобразуем двудольный граф в сеть, добавив искусственный источник (`s`) и сток (`t = s + 1`).

Источник (`s`) имел ребра единичной пропускной способности ко всем вершинам, представляющим пункты на левом берегу. Все вершины, представляющие пункты на правом берегу, имели ребра единичной пропускной способности к стоку (`t`). Рёбра между пунктами левого и правого берегов (представляющие мосты) также имели единичную пропускную способность.

Это преобразование позволило свести задачу поиска максимального паросочетания к задаче поиска максимального потока в созданной сети.

Для поиска максимального потока используем модификацию алгоритма Эдмондса-Карпа, реализуя обход в ширину (BFS) для поиска увеличивающего пути.

Функция `BFS` реализована следующим образом:
```C++
inline size_t BFS(size_t s, std::vector<ssize_t>& parent,
std::vector<std::vector<size_t>>& adj_list,
std::vector<std::vector<size_t>>& capacity) {
parent[s] = -2;
size_t n = parent.size();

std::vector<bool> visited(n, false);
visited[s] = true;

std::queue<std::pair<size_t, size_t>> nodes;

nodes.push({s, LLONG_MAX});

while (!nodes.empty()) {
std::pair<size_t, size_t> node_flow = nodes.front();
size_t node = node_flow.first;
size_t flow = node_flow.second;

nodes.pop();

for (size_t i = 0; i < adj_list[node].size(); i++) {
size_t next = adj_list[node][i];

if (visited[next] || capacity[node][i] == 0) continue;

visited[next] = true;

parent[next] = node;

size_t new_flow = std::min(flow, capacity[node][i]);

if (next == s + 1) return new_flow;
nodes.push({next, new_flow});
}
}

return 0;
}
```

В этой функции `parent` используется для отслеживания пути, `adj_list` - список смежности, `capacity` - матрица пропускных способностей. Функция возвращает пропускную способность найденного увеличивающего пути, или 0, если путь не найден.

#### Реализация алгоритма максимального потока:

Алгоритм максимального потока реализован в функции `MaxFlow`:
```C++
inline size_t MaxFlow(size_t s, std::vector<std::vector<size_t>>& adj_list,
std::vector<std::vector<size_t>>& capacity) {
size_t flow = 0;
std::vector<ssize_t> parent(adj_list.size(), -1);

size_t new_flow = 0;

while ((new_flow = BFS(s, parent, adj_list, capacity))) {
flow += new_flow;
size_t curr = s + 1;

while (curr != s) {
size_t prev = parent[curr];
size_t idx = (find(adj_list[prev].begin(), adj_list[prev].end(), curr) -
adj_list[prev].begin());
capacity[prev][idx] -= new_flow;

idx = (find(adj_list[curr].begin(), adj_list[curr].end(), prev) -
adj_list[curr].begin());
capacity[curr][idx] += new_flow;

curr = prev;
}
}

return flow;
}

```

Функция `MaxFlow` инициализирует поток в 0, и пока находит увеличивающие пути с помощью `BFS`, наращивает поток и обновляет пропускные способности остаточных ребер. Возвращает величину максимального потока.

#### Преобразование двудольного графа в сеть и нахождение максимального паросочетания

Преобразование двудольного графа в сеть и использование алгоритма максимального потока для нахождения максимального паросочетания реализовано в функции `MaximumBipartiteMatching`:
```C++
inline size_t MaximumBipartiteMatching(size_t n, size_t m,
Graph<size_t>& bipartite_graph) {
std::vector<std::vector<std::pair<size_t, size_t>>> bipartite_edges_stack(
n + m + 3);
std::vector<std::vector<size_t>> adj_list(n + m + 3);
std::vector<std::vector<size_t>> capacity(n + m + 3);

for (size_t i = 0; i < bipartite_graph.EdgesAmount(); i++) {
size_t u = StartVertFromTuple(bipartite_graph.Edges()[i]);
size_t v = EndVertFromTuple(bipartite_graph.Edges()[i]);

v += n;

bipartite_edges_stack[u].push_back({v, 1});
bipartite_edges_stack[v].push_back({u, 0});
}

for (size_t i = 1; i <= n; i++) {
bipartite_edges_stack[n + m + 1].push_back({i, 1});
bipartite_edges_stack[i].push_back({n + m + 1, 0});
}
for (size_t i = 1; i <= m; i++) {
bipartite_edges_stack[i + n].push_back({n + m + 2, 1});
bipartite_edges_stack[n + m + 2].push_back({i + n, 0});
}

for (size_t i = 1; i <= n + m + 2; i++)
sort(bipartite_edges_stack[i].begin(), bipartite_edges_stack[i].end());

for (size_t i = 1; i <= n + m + 2; i++)
for (size_t j = 0; j < bipartite_edges_stack[i].size(); j++) {
adj_list[i].push_back(bipartite_edges_stack[i][j].first);
capacity[i].push_back(bipartite_edges_stack[i][j].second);
}

return MaxFlow(n + m + 1, adj_list, capacity);
}

```

В этой функции двудольный граф `bipartite_graph` преобразуется в сеть с помощью `bipartite_edges_stack`, добавляется источник (`n + m + 1`) и сток (`n + m + 2`), затем строится матрица смежности `adj_list` и матрица пропускных способностей `capacity` и вызывается функция MaxFlow для вычисления максимального потока.

#### Вызов функции решения и заключение:

Функция `Solution` считывает входные данные и выводит результат:

```C++
inline void Solution(std::istream& is = std::cin,
std::ostream& os = std::cout) {
size_t n, m;
is >> n >> m;

size_t p;
is >> p;

Graph<size_t> bipartite_graph;

for (size_t i = 0; i < p; i++) {
size_t u, v;
is >> u >> v;

bipartite_graph.AddEdge({u, v});
}

os << MaximumBipartiteMatching(n, m, bipartite_graph) << std::endl;
return;
}
```

В результате, величина максимального потока, возвращаемая функцией `MaximumBipartiteMatching` и являющаяся размером максимального паросочетания, дает размер минимального вершинного покрытия, что и являлось ответом к задаче – минимальное количество необходимых часовых.
189 changes: 189 additions & 0 deletions additional_tasks/bridge_guards/src/bridge_guards.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
#include <climits>
#include <queue>

#include "graph.hpp"

/**
* @brief Функция BFS (поиск в ширину) для нахождения увеличивающего пути в
* сети.
*
* @param s: начальная вершина (источник) в сети.
* @param parent: массив, хранящий родительские вершины для восстановления пути.
* @param adj_list: список смежности графа.
* @param capacity: матрица пропускных способностей ребер.
*
* @return `size_t`: пропускная способность найденного увеличивающего пути, или
* 0, если путь не найден.
*/
inline size_t BFS(size_t s, std::vector<ssize_t>& parent,
std::vector<std::vector<size_t>>& adj_list,
std::vector<std::vector<size_t>>& capacity) {
// инициализация родительского массива и массива посещенных вершин

parent[s] = -2; // отмечаем начальную вершину как посещенную, значение -2
// используется для обозначения источника
size_t n = parent.size();

std::vector<bool> visited(n, false);
visited[s] = true;

// используем очередь для обхода графа в ширину
std::queue<std::pair<size_t, size_t>> nodes;

// добавляем начальную вершину с максимальным потоком
nodes.push({s, LLONG_MAX});

// обходим граф до тех пор, пока очередь не пуста
while (!nodes.empty()) {
// извлекаем вершину и её текущий поток из очереди
std::pair<size_t, size_t> node_flow = nodes.front();
size_t node = node_flow.first;
size_t flow = node_flow.second;

nodes.pop();

// проверяем все ребра, исходящие из текущей вершины
for (size_t i = 0; i < adj_list[node].size(); i++) {
size_t next = adj_list[node][i]; // следующая вершина

// если следующая вершина уже посещена или пропускная способность ребра
// равна 0, то пропускаем ребро
if (visited[next] || capacity[node][i] == 0) continue;

// отмечаем следующую вершину как посещенную
visited[next] = true;

// записываем родительскую вершину
parent[next] = node;

// вычисляем новый поток как min из текущего потока и пропускной
// способности ребра
size_t new_flow = std::min(flow, capacity[node][i]);

// если достигли стока, то возвращаем найденный поток
if (next == s + 1) return new_flow;
// иначе добавляем следующую вершину в очередь с новым потоком
nodes.push({next, new_flow});
}
}

// увеличивающий путь не найден, возвращаем 0
return 0;
}

/**
* @brief Функция для вычисления максимального потока в сети.
*
* @param s: начальная вершина (источник) в сети.
* @param adj_list: список смежности графа.
* @param capacity: матрица пропускных способностей ребер.
*
* @return `size_t`: величина максимального потока в сети.
*/
inline size_t MaxFlow(size_t s, std::vector<std::vector<size_t>>& adj_list,
std::vector<std::vector<size_t>>& capacity) {
size_t flow = 0; // инициализируем максимальный поток нулем
std::vector<ssize_t> parent(adj_list.size(), -1); // родительский массив

size_t new_flow = 0;

// повторяем поиск увеличивающих путей до тех пор, пока они находятся
while ((new_flow = BFS(s, parent, adj_list, capacity))) {
// добавляем найденный поток к максимальному потоку
flow += new_flow;

size_t curr = s + 1; // начинаем с стока

// обновляем пропускные способности ребер по найденному пути
while (curr != s) {
size_t prev = parent[curr];
size_t idx = (find(adj_list[prev].begin(), adj_list[prev].end(), curr) -
adj_list[prev].begin());
capacity[prev][idx] -= new_flow;

idx = (find(adj_list[curr].begin(), adj_list[curr].end(), prev) -
adj_list[curr].begin());
capacity[curr][idx] += new_flow;

curr = prev;
}
}

return flow; // максимальный поток
}

/**
* @brief Функция для вычисления максимального паросочетания в двудольном графе.
*
* @param n: количество вершин в первой доле графа.
* @param m: количество вершин во второй доле графа.
* @param bipartite_graph: объект, представляющий двудольный граф.
*
* @return `size_t`: размер максимального паросочетания.
*/
inline size_t MaximumBipartiteMatching(size_t n, size_t m,
Graph<size_t>& bipartite_graph) {
// вспомогательные структуры данных для алгоритма Форда-Фалкерсона
std::vector<std::vector<std::pair<size_t, size_t>>> bipartite_edges_stack(
n + m + 3);
std::vector<std::vector<size_t>> adj_list(n + m + 3);
std::vector<std::vector<size_t>> capacity(n + m + 3);

// преобразуем двудольный граф в сеть

for (size_t i = 0; i < bipartite_graph.EdgesAmount(); i++) {
size_t u = StartVertFromTuple(bipartite_graph.Edges()[i]);
size_t v = EndVertFromTuple(bipartite_graph.Edges()[i]);

v += n; // смещаем номера вершин второй доли

bipartite_edges_stack[u].push_back({v, 1});
bipartite_edges_stack[v].push_back(
{u, 0}); // обратное ребро с пропускной способностью 0
}

// добавляем источник и сток
for (size_t i = 1; i <= n; i++) {
bipartite_edges_stack[n + m + 1].push_back({i, 1});
bipartite_edges_stack[i].push_back({n + m + 1, 0});
}
for (size_t i = 1; i <= m; i++) {
bipartite_edges_stack[i + n].push_back({n + m + 2, 1});
bipartite_edges_stack[n + m + 2].push_back({i + n, 0});
}

// сортируем ребра для удобства работы
for (size_t i = 1; i <= n + m + 2; i++)
sort(bipartite_edges_stack[i].begin(), bipartite_edges_stack[i].end());

// списки смежности и пропускных способностей
for (size_t i = 1; i <= n + m + 2; i++)
for (size_t j = 0; j < bipartite_edges_stack[i].size(); j++) {
adj_list[i].push_back(bipartite_edges_stack[i][j].first);
capacity[i].push_back(bipartite_edges_stack[i][j].second);
}

// максимальный поток, который равен максимальному паросочетанию
return MaxFlow(n + m + 1, adj_list, capacity);
}

inline void Solution(std::istream& is = std::cin,
std::ostream& os = std::cout) {
size_t n, m;
is >> n >> m;

size_t p;
is >> p;

Graph<size_t> bipartite_graph;

for (size_t i = 0; i < p; i++) {
size_t u, v;
is >> u >> v;

bipartite_graph.AddEdge({u, v});
}

os << MaximumBipartiteMatching(n, m, bipartite_graph) << std::endl;
return;
}
6 changes: 6 additions & 0 deletions additional_tasks/bridge_guards/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "bridge_guards.hpp"

int main() {
Solution();
return 0;
}
251 changes: 251 additions & 0 deletions additional_tasks/bridge_guards/src/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
#include <gtest/gtest.h>

#include "bridge_guards.hpp"

TEST(BridgeGuardsTest, SimpleTest) {
std::stringstream ss;
ss << "1 1\n"
<< "1\n"
<< "1 1\n";

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "1\n");
}

TEST(BridgeGuardsTest, Test_1) {
std::stringstream ss;
ss << "4 3\n"
<< "4\n"
<< "1 3\n"
<< "1 2\n"
<< "2 1\n"
<< "2 3\n";

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "2\n");
}

TEST(BridgeGuardsTest, Test_2) {
std::stringstream ss;
ss << "4 3\n"
<< "4\n"
<< "1 3\n"
<< "1 2\n"
<< "2 2\n"
<< "4 1\n";

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "3\n");
}

TEST(BridgeGuardsTest, Test_3) {
std::stringstream ss;
ss << "4 4\n"
<< "4\n"
<< "1 1\n"
<< "2 2\n"
<< "3 3\n"
<< "4 4\n";

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "4\n");
}

TEST(BridgeGuardsTest, Test_4) {
std::stringstream ss;
ss << "5 2\n"
<< "5\n"
<< "1 1\n"
<< "2 1\n"
<< "3 1\n"
<< "4 2\n"
<< "5 2\n";

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "2\n");
}

TEST(BridgeGuardsTest, HardTest) {
std::stringstream ss;
ss << "10 6\n"
<< "12\n"
<< "1 1\n"
<< "2 1\n"
<< "3 1\n"
<< "4 3\n"
<< "5 3\n"
<< "6 2\n"
<< "7 3\n"
<< "8 3\n"
<< "9 4\n"
<< "9 5\n"
<< "10 5\n"
<< "10 6\n";

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "5\n");
}

TEST(BridgeGuardsTest, EmptyGraph) {
std::stringstream ss;
ss << "0 0\n" // 0 вершин в обеих долях
<< "0\n"; // 0 мостов

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "0\n");
}

TEST(BridgeGuardsTest, NoEdges) {
std::stringstream ss;
ss << "5 3\n" // вершин в первой доле, 3 во второй
<< "0\n"; // 0 мостов

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "0\n");
}

TEST(BridgeGuardsTest, OneSidedGraph) {
std::stringstream ss;
ss << "5 0\n" // 5 вершин в первой доле, 0 во второй
<< "0\n"; // 0 мостов

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "0\n");
}

TEST(BridgeGuardsTest, OneSidedGraph2) {
std::stringstream ss;
ss << "0 3\n" // 0 вершин в первой доле, 3 во второй
<< "0\n"; // 0 мостов

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "0\n");
}

TEST(BridgeGuardsTest, LargeGraphSmallEdges) {
std::stringstream ss;
ss << "100 100\n" // 100 вершин в каждой доле
<< "5\n" // 5 мостов
<< "1 1\n"
<< "2 2\n"
<< "3 3\n"
<< "4 4\n"
<< "5 5\n";

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "5\n");
}

TEST(BridgeGuardsTest, CompleteBipartiteGraph) {
std::stringstream ss;
ss << "3 3\n"
<< "9\n"
<< "1 1\n"
<< "1 2\n"
<< "1 3\n"
<< "2 1\n"
<< "2 2\n"
<< "2 3\n"
<< "3 1\n"
<< "3 2\n"
<< "3 3\n";

std::stringstream output;

Solution(ss, output);
EXPECT_EQ(output.str(), "3\n");
}

TEST(BridgeGuardsTest, StarShapedGraph) {
std::stringstream ss;
ss << "5 1\n"
<< "5\n"
<< "1 1\n"
<< "2 1\n"
<< "3 1\n"
<< "4 1\n"
<< "5 1\n";

std::stringstream output;
Solution(ss, output);
EXPECT_EQ(output.str(), "1\n");
}

TEST(BridgeGuardsTest, StarShapedGraph2) {
std::stringstream ss;
ss << "1 5\n"
<< "5\n"
<< "1 1\n"
<< "1 2\n"
<< "1 3\n"
<< "1 4\n"
<< "1 5\n";

std::stringstream output;
Solution(ss, output);
EXPECT_EQ(output.str(), "1\n");
}

TEST(BridgeGuardsTest, DisconnectedGraph) {
std::stringstream ss;
ss << "6 6\n"
<< "4\n"
<< "1 1\n"
<< "2 2\n"
<< "3 3\n"
<< "4 4\n";

std::stringstream output;
Solution(ss, output);
EXPECT_EQ(output.str(), "4\n");
}

TEST(BridgeGuardsTest, Test_5) {
std::stringstream ss;
ss << "3 2\n"
<< "4\n"
<< "1 1\n"
<< "1 2\n"
<< "2 1\n"
<< "3 1\n";

std::stringstream output;
Solution(ss, output);
EXPECT_EQ(output.str(), "2\n");
}

TEST(BridgeGuardsTest, Test_6) {
std::stringstream ss;
ss << "2 3\n"
<< "4\n"
<< "1 1\n"
<< "1 2\n"
<< "2 2\n"
<< "2 3\n";

std::stringstream output;
Solution(ss, output);
EXPECT_EQ(output.str(), "2\n");
}
39 changes: 39 additions & 0 deletions additional_tasks/chem_experiments_chain/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
Utils
)

include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME}_tests)
73 changes: 73 additions & 0 deletions additional_tasks/chem_experiments_chain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Задача: обратимые состояния в цепочке химических экспериментов

В прогрессивной Московской химической лаборатории используют новые методы описания цепочки экспериментов: лаборанты изображают их в виде графов, вершинами которого являются состояния вещества, а эксперименты - ребрами.

Таким образом компании, разрабатывающей программное обеспечение, используя методы хемоинформатики, очень удобно использовать эксперименты этой лаборатории в своих наработках. В одном из проектов им потребовалось найти исключительно полезные состояния, обладающие свойством обратимости в цепочке.

К обратимым экспериментам относят те, результирующее состояние вещества которых можно вернуть к исходному, через другие процессы в той же цепочке; в таком случае результирующее состояния вещества также называют обратимым.

Найдите по известной цепочке экспериментов, оформленной в виде графа, эти обратимые состояния.

## Входные данные:

* Первая строка содержит целое число `N` (`N` > 0) — количество экспериментов в графе.
* Следующие `N` строк содержат по два поля, разделенных пробелом: `A` `B`, где `A` и `B` — это строковые имена вершин (состояния веществ). Ребро идет от вершины `A` к вершине `B`. Имена вершин могут содержать любые символы, кроме пробела, переноса строки и пр.

## Выходные данные:

* Выходные данные представляют собой строку, содержащую через пробел имена всех обратимых состояний (порядок вывода обратимых состояний не важен, состояния выводятся без повторений)
* Если обратимых состояний нет, то вывод должен быть пустым.

# Используемый алгоритм: нахождение компонент сильной связности алгоритмом Тарьяна

## Как работает алгоритм Тарьяна:

Алгоритм Тарьяна - это подход к решению задачи поиска компонент сильной связности (СС) в ориентированном графе. Он работает за линейное время (`O(|V|+|E|)`, где `V` - количество вершин, а `E` - количество ребер) и использует рекурсию для эффективного обхода графа.

Алгоритм Тарьяна основывается на том, что вершина `v` является корнем СС тогда и только тогда, когда ее “низкая связь” (`low_links`) равна ее “времени входа” (`indexes`). “Низкая связь” - это минимальное “время входа” всех вершин, которые можно достичь из `v` по пути рекурсии.

Иначе говоря, вершины рассматриваются в обратном топологическом порядке, поэтому в конце рекурсивной функции для исходной вершины не будет встречено ни одной вершины из той же компоненты сильной связности, так как все вершины, достижимые из исходной, уже обработаны, и обратные связи в дереве дают второй путь из одной вершины в другую и связывают компоненты сильной связности в одну.

## Объяснение кода:

```C++
template <typename vert_t>
static void StronglyConnectedComponentsStep(
const vert_t& v, size_t& curr_index, std::stack<vert_t>& verts_stack,
std::unordered_map<vert_t, size_t>& indexes,
std::unordered_map<vert_t, size_t>& low_links,
std::unordered_map<vert_t, bool>& is_on_stack,
std::unordered_map<vert_t, std::vector<vert_t>>& adj_list,
std::vector<std::vector<vert_t>>& strongly_connected_components);
```
С помощью функции выше выполняется рекурсивный обход.
Для каждой вершины `v`, которая еще не была посещена, выполняется рекурсивный обход. При первом посещении вершины `v`, `indexes[v]` устанавливается равным `curr_index`, `low_links[v]` также устанавливается равным `curr_index`. `curr_index` увеличивается на 1. Вершина `v` помещается в стек `verts_stack`.
Для каждой смежной вершины `w` вершине `v` выполняется следующее:
1. Если w не была посещена (```indexes[w] == 0```): выполняется рекурсивный вызов StronglyConnectedComponentsStep для вершины `w`, `low_links[v]` обновляется минимальным значением между текущим значением и `low_links[w]`.
2. Если w уже находится в стеке (`is_on_stack[w]`):
`low_links[v]` обновляется минимальным значением между текущим значением и `low_links[w]`.
После обработки всех смежных вершин, если ```low_links[v] == indexes[v]```, это означает, что `v` является корнем СС.
В этом случае все вершины, которые находятся в стеке от v до вершины, которая была обработана ранее, также принадлежат этой СС.
Вершины удаляются из стека, пока v не будет удален. Эти удаленные вершины образуют СС, которая добавляется в список СС.
```C++
/**
* @brief Поиск компонент сильной связности в ориентированного графа по
* алгоритму Тарьяна
* @tparam vert_t: тип вершин
* @tparam weight_t: тип весов
* @param graph: исходный граф
* @throw std::invalid_argument("StronglyConnectedComponents: graph is not
* directed.");
* @return std::vector<std::vector<vert_t>>: компоненты сильной связности
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
std::vector<std::vector<vert_t>> StronglyConnectedComponents(
Graph<vert_t, weight_t> graph);
```

Основная функция выше вызывает ```StronglyConnectedComponentsStep(...)``` для каждой вершины графа и возвращает список компонент.
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include "tarjan_algorithm.hpp"

/// @brief Решает задачу: "обратимые состояния в цепочке химических
/// экспериментов"
void Solution(std::istream& is = std::cin, std::ostream& os = std::cout) {
size_t experiments_amount;
is >> experiments_amount;

Graph<std::string> graph;
graph.MakeDirected();

for (size_t i = 0; i < experiments_amount; i++) {
std::string u, v;
is >> u >> v;

graph.AddEdge({u, v});
graph.AddVert(u);
graph.AddVert(v);
}

for (const auto& component : StronglyConnectedComponents(graph))
if (component.size() != 1 && !component.empty())
for (const auto& elem : component) os << elem << " ";

os << std::endl;
}
6 changes: 6 additions & 0 deletions additional_tasks/chem_experiments_chain/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "chem_experiments_chain.hpp"

int main() {
Solution();
return 0;
}
108 changes: 108 additions & 0 deletions additional_tasks/chem_experiments_chain/src/tarjan_algorithm.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#pragma once

#include <algorithm>
#include <stack>
#include <stdexcept>

#include "graph.hpp"

namespace {

template <AllowedVertType vert_t>
inline void StronglyConnectedComponentsStep(
const vert_t& vert, size_t& curr_index, std::stack<vert_t>& verts_stack,
std::unordered_map<vert_t, size_t>& indexes,
std::unordered_map<vert_t, size_t>& low_links,
std::unordered_map<vert_t, bool>& is_on_stack,
std::unordered_map<vert_t, std::vector<vert_t>>& adj_list,
std::vector<std::vector<vert_t>>& strongly_connected_components) {
// в curr_index храним количество ранее обработанных вершин,
// indexes[vert] - это "время входа" в вершину vert
indexes[vert] = low_links[vert] = curr_index++;

verts_stack.push(vert);

// is_on_stack нужно, чтобы проверять принадлежность вершины стеку за O(1)
is_on_stack[vert] = true;

// перебираем рёбра, исходящие из vert
for (auto& u_vert : adj_list[vert]) {
if (indexes[u_vert] == 0) {
// вершина u_vert ранее не посещалась; запускаемся из неё рекурсивно
StronglyConnectedComponentsStep(u_vert, curr_index, verts_stack, indexes,
low_links, is_on_stack, adj_list,
strongly_connected_components);

low_links[vert] = std::min(low_links[vert], low_links[u_vert]);
} else if (is_on_stack[u_vert])
// вершина u_vert находится в стеке, значит, принадлежит той же компоненте
// сильной связности, что и vert

// если u_vert не в стеке, значит, ребро (vert, u_vert) ведёт в ранее
// обработанную компоненту сильной связности и должна быть проигнорирована
low_links[vert] = std::min(low_links[vert], low_links[u_vert]);
}

// вершина vert - корень текущей компоненты сильной связности,
// все вершины в стеке от vert и выше образуют эту компоненту
if (low_links[vert] == indexes[vert]) {
vert_t u_vert;
std::vector<vert_t> strongly_connected_component;

do {
u_vert = verts_stack.top();
verts_stack.pop();

is_on_stack[u_vert] = false;
strongly_connected_component.push_back(u_vert);
} while (u_vert != vert);

strongly_connected_components.push_back(strongly_connected_component);
}
}

} // namespace

/**
* @brief Поиск компонент сильной связности в ориентированного графа по
* алгоритму Тарьяна.
* @tparam vert_t: тип вершин
* @tparam weight_t: тип весов
* @param graph: исходный граф
* @throw `std::invalid_argument("StronglyConnectedComponents: graph is not
* directed.")`.
* @return `std::vector<std::vector<vert_t>>`: компоненты сильной связности
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
std::vector<std::vector<vert_t>> StronglyConnectedComponents(
const Graph<vert_t, weight_t>& graph) {
if (!graph.IsDirected())
throw std::invalid_argument(
"StronglyConnectedComponents: graph is not directed.");

if (graph.Verts().empty()) return {};

std::vector<std::vector<vert_t>> strongly_connected_component;

std::stack<vert_t> verts_stack;
size_t curr_index = 0;

std::unordered_map<vert_t, std::vector<vert_t>> adj_list = graph.GetAdjList();

std::unordered_map<vert_t, size_t> indexes;
std::unordered_map<vert_t, size_t> low_links;
std::unordered_map<vert_t, bool> is_on_stack;

for (const auto& vert : graph.Verts()) {
indexes[vert] = low_links[vert] = 0;
is_on_stack[vert] = false;
}

for (const auto& vert : graph.Verts())
if (indexes[vert] == 0)
StronglyConnectedComponentsStep(vert, curr_index, verts_stack, indexes,
low_links, is_on_stack, adj_list,
strongly_connected_component);

return strongly_connected_component;
}
172 changes: 172 additions & 0 deletions additional_tasks/chem_experiments_chain/src/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#include <gtest/gtest.h>

#include "tarjan_algorithm.hpp"

TEST(SCCSTA_Test, Simple) {
std::unordered_map<std::string, std::vector<std::string>> adj_list_dict = {
{"A", {"B"}}, {"B", {"C", "D"}}, {"C", {"A", "D"}}, {"D", {"E"}},
{"E", {"D"}}, {"F", {"E", "G"}}, {"G", {"F", "H"}}, {"H", {"E", "G"}}};

auto graph = Graph<std::string, long>::GraphFromAdjList(adj_list_dict);

std::vector<std::vector<std::string>> answer = {
{"D", "E"}, {"H", "G", "F"}, {"A", "C", "B"}};

ASSERT_EQ(StronglyConnectedComponents(graph), answer);
}

TEST(SCCSTA_Test, EmptyGraph) {
Graph<int, long> graph;
auto components = StronglyConnectedComponents(graph);

ASSERT_EQ(components.size(), 0);
}

TEST(SCCSTA_Test, SingleVertex) {
Graph<int, long> graph;
graph.AddVert(1);

auto components = StronglyConnectedComponents(graph);

ASSERT_EQ(components.size(), 1);

ASSERT_EQ(components[0], std::vector<int>({1}));
}

TEST(SCCSTA_Test, TwoVerticesNoEdges) {
Graph<int, long> graph;
graph.AddVert(1);
graph.AddVert(2);

auto components = StronglyConnectedComponents(graph);
ASSERT_EQ(components.size(), 2);

ASSERT_EQ(components[0], std::vector<int>({1}));
ASSERT_EQ(components[1], std::vector<int>({2}));
}

TEST(SCCSTA_Test, SimpleCycle) {
Graph<int, long> graph;
graph.AddVert(1);
graph.AddVert(2);
graph.AddEdge({1, 2});
graph.AddEdge({2, 1});
auto components = StronglyConnectedComponents(graph);
ASSERT_EQ(components.size(), 1);
ASSERT_EQ(components[0], std::vector<int>({1, 2}));
}

TEST(SCCSTA_Test, MultipleCycles) {
Graph<int, long> graph;
graph.AddVert(1);
graph.AddVert(2);
graph.AddVert(3);
graph.AddVert(4);
graph.AddEdge({1, 2});
graph.AddEdge({2, 1});
graph.AddEdge({3, 4});
graph.AddEdge({4, 3});

auto components = StronglyConnectedComponents(graph);

ASSERT_EQ(components.size(), 2);

std::sort(components[0].begin(), components[0].end());
std::sort(components[1].begin(), components[1].end());

ASSERT_EQ(components[0], std::vector<int>({1, 2}));
ASSERT_EQ(components[1], std::vector<int>({3, 4}));
}

TEST(SCCSTA_Test, ComplexGraph) {
Graph<int, long> graph;
graph.AddVert(1);
graph.AddVert(2);
graph.AddVert(3);
graph.AddVert(4);
graph.AddVert(5);
graph.AddVert(6);
graph.AddEdge({1, 2});
graph.AddEdge({2, 3});
graph.AddEdge({3, 1});
graph.AddEdge({4, 5});
graph.AddEdge({5, 6});
graph.AddEdge({6, 4});
graph.AddEdge({2, 4});

auto components = StronglyConnectedComponents(graph);

ASSERT_EQ(components.size(), 2);

std::sort(components.begin(), components.end());

std::sort(components[0].begin(), components[0].end());
std::sort(components[1].begin(), components[1].end());

ASSERT_EQ(components[0], std::vector<int>({1, 2, 3}));
ASSERT_EQ(components[1], std::vector<int>({4, 5, 6}));
}

TEST(SCCSTA_Test, UndirectedGraph) {
Graph<int, long> graph;
graph.AddVert(1);
graph.AddVert(2);
graph.AddEdge({1, 2});

graph.MakeUndirected();

ASSERT_THROW(StronglyConnectedComponents(graph), std::invalid_argument);
}

TEST(SCCSTA_Test, SelfLoop) {
Graph<int, long> graph;
graph.AddVert(1);
graph.AddEdge({1, 1});

auto components = StronglyConnectedComponents(graph);

ASSERT_EQ(components.size(), 2);

ASSERT_EQ(components[0], std::vector<int>({1}));
ASSERT_EQ(components[1], std::vector<int>({1}));
}

TEST(SCCSTA_Test, MultipleSelfLoops) {
Graph<int, long> graph;
graph.AddVert(1);
graph.AddVert(2);
graph.AddEdge({1, 1});
graph.AddEdge({2, 2});

auto components = StronglyConnectedComponents(graph);

ASSERT_EQ(components.size(), 3);

ASSERT_EQ(components[0], std::vector<int>({1}));
ASSERT_EQ(components[2], std::vector<int>({2}));
}

TEST(SCCSTA_Test, ConnectedComponents) {
Graph<int, long> graph;
graph.AddVert(1);
graph.AddVert(2);
graph.AddVert(3);
graph.AddVert(4);
graph.AddEdge({1, 2});
graph.AddEdge({2, 1});
graph.AddEdge({3, 4});
graph.AddEdge({4, 3});
graph.AddEdge({1, 3});

auto components = StronglyConnectedComponents(graph);

ASSERT_EQ(components.size(), 2);

std::sort(components.begin(), components.end());

std::sort(components[0].begin(), components[0].end());
std::sort(components[1].begin(), components[1].end());

ASSERT_EQ(components[0], std::vector<int>({1, 2}));
ASSERT_EQ(components[1], std::vector<int>({3, 4}));
}
39 changes: 39 additions & 0 deletions additional_tasks/find_word_chains/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
Utils
)

include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME}_tests)
32 changes: 32 additions & 0 deletions additional_tasks/find_word_chains/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Задача: можно ли объединить список слов в цепочку

Дан набор слов. Требуется определить, можно ли расположить эти слова в таком порядке, чтобы последняя буква каждого слова совпадала с первой буквой следующего слова, образуя замкнутую цепочку.

Например, слова “for”, “rig”, “geek”, “kaf” можно соединить в цепочку (for -> rig -> geek -> kaf -> for), в то время как для слов “aab”, “abb” это невозможно.

## Входные данные:

* Первая строка содержит целое число `N` (`N` > 0) — количество слов.
* Следующая строка содержит `N` строковых литералов, описывающих набор **слов**.

## Выходные данные:

* Выходные данные представляют собой строку, содержащую `Yes`, если слова образуют цепочку и `No`, если нет.

## Решение:

Для решения задачи используется подход, основанный на теории графов. Набор слов преобразуется в ориентированный граф следующим образом:

1. **Построение графа:** Каждая буква английского алфавита (a-z) представляет собой вершину графа. Для каждого слова создаётся ориентированное ребро, соединяющее первую букву слова с его последней буквой. Например, для слова "for" будет создано ребро от вершины 'f' к вершине 'r'.

2. **Проверка на Эйлеров цикл:** Задача сводится к проверке существования Эйлерова цикла в построенном графе. Эйлеров цикл — это циклический путь, проходящий через каждое ребро графа ровно один раз. Для существования эйлерова цикла необходимо и достаточно выполнение следующих условий:

* **Степень вершин:** В ориентированном графе для каждой вершины количество входящих ребер должно быть равно количеству исходящих ребер.
* **Связность:** Граф должен быть сильно связным, то есть из любой вершины должна быть достижима любая другая вершина, двигаясь по ребрам.

3. **Алгоритм проверки:** Алгоритм реализует проверку условий существования эйлерова цикла:

* **Подсчет степеней:** Для каждой вершины подсчитываются входящая и исходящая степени. Если хотя бы для одной вершины эти степени не равны, эйлеров цикл невозможен.
* **Проверка на связность:** Используется алгоритм поиска в глубину (Depth-First Search, DFS). Начинается обход с произвольной вершины, имеющей исходящее ребро. Если после DFS не все вершины с ненулевой степенью были посещены, значит граф не сильно связный, а значит, и эйлерова цикла нет.

Если оба условия выполнены, алгоритм возвращает "Yes", в противном случае — "No".
148 changes: 148 additions & 0 deletions additional_tasks/find_word_chains/src/euler_path.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#pragma once

#include "graph.hpp"

namespace {

/**
* @brief
*
* @tparam vert_t
* @tparam weight_t
* @param v
* @param graph
* @param visited
*/

/**
* @brief Вспомогательная рекурсивная функция для обхода графа в глубину (DFS).
*
* @details Проверяет связность графа, начиная с вершины `v`. Эта функция
* используется внутри `HasEulerPath` для проверки связности после проверки
* количества вершин с нечётной степенью.
*
* @tparam vert_t: тип вершин графа.
* @tparam weight_t: тип весов рёбер графа.
* @param v: начальная вершина для DFS.
* @param graph: граф, в котором выполняется обход.
* @param visited: посещенные вершины.
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
inline void HasEulerPathStep(const vert_t& v,
const Graph<vert_t, weight_t>& graph,
std::unordered_map<vert_t, bool>& visited) {
visited[v] = true;

// получаем соседнюю вершину (откуда идёт ребро), если соседняя вершина ещё не
// посещена, рекурсивно вызываем DFS для неё.

for (const auto& edge_tuple : graph.Edges()) {
if (StartVertFromTuple(edge_tuple) == v) {
vert_t neighbor = EndVertFromTuple(edge_tuple);

if (!visited[neighbor]) HasEulerPathStep(neighbor, graph, visited);
}

if (EndVertFromTuple(edge_tuple) == v && !graph.IsDirected()) {
vert_t neighbor = StartVertFromTuple(edge_tuple);

if (!visited[neighbor]) HasEulerPathStep(neighbor, graph, visited);
}
}
}

/**
* @brief Вычисляет исходящую степень вершины в графе.
*
* @tparam vert_t: тип вершин графа.
* @tparam weight_t: тип весов рёбер графа.
* @param graph: граф, в котором вычисляется исходящая степень.
* @param v: вершина, для которой вычисляется исходящая степень.
*
* @return size_t: исходящая степень вершины (количество исходящих ребер).
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
inline size_t OutDeg(const Graph<vert_t, weight_t>& graph, const vert_t& v) {
return graph.GetAdjList()[v].size();
}

/**
* @brief Вычисляет входящую степень вершины в графе.
*
* @tparam vert_t: тип вершин графа.
* @tparam weight_t: тип весов рёбер графа.
* @param graph: граф, в котором вычисляется входящая степень.
* @param v: вершина, для которой вычисляется входящая степень.
*
* @return size_t: входящая степень вершины (количество входящих ребер).
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
inline size_t InDeg(const Graph<vert_t, weight_t>& graph, const vert_t& v) {
// для неориентированных графов входящая степень равна исходящей
if (!graph.IsDirected()) return OutDeg(graph, v);

size_t res = 0;

auto adj_list = graph.GetAdjList();

for (const auto& u : graph.Verts())
if (Contains(adj_list[u], v)) res++;

return res;
}

} // namespace

/**
* @brief Проверяет, существует ли в графе эйлеров путь.
*
* @details Эйлеров путь — это путь в графе, который проходит через каждое ребро
* ровно один раз. Функция сначала проверяет необходимое, но недостаточное
* условие существования эйлерова пути: количество вершин с нечетной степенью
* должно быть не больше двух. Затем она проверяет связность графа с помощью
* DFS.
*
* @tparam vert_t: тип вершин графа.
* @tparam weight_t: тип весов рёбер графа.
* @param graph: граф, для которого проверяется наличие эйлерова пути.
*
* @return `true`, если эйлеров путь существует
* @return `false` в противном случае.
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
inline bool HasEulerPath(const Graph<vert_t, weight_t>& graph) {
// считаем количество вершин с нечетной степенью
size_t odd_vert_count = 0;

for (const auto& v : graph.Verts())
if (OutDeg(graph, v) != InDeg(graph, v))
odd_vert_count++;

else if (!graph.IsDirected() && OutDeg(graph, v) % 2 != 0)
odd_vert_count++;

// если вершин с нечетной степенью больше двух, то эйлерова пути нет
if (odd_vert_count > 2) return false;

std::unordered_map<vert_t, bool> visited;
for (const auto& vert : graph.Verts()) visited[vert] = false;

std::unordered_map<vert_t, size_t> degree;
for (const auto& vert : graph.Verts())
degree[vert] = graph.IsDirected() ? InDeg(graph, vert) + OutDeg(graph, vert)
: OutDeg(graph, vert);

// находим первую вершину с ненулевой степенью и начинаем DFS
for (const auto& v : graph.Verts())
if (degree[v] > 0) {
HasEulerPathStep(v, graph, visited);
break;
}

// проверяем, все ли вершины с ненулевой степенью были посещены.
for (const auto& v : graph.Verts())
if (degree[v] > 0 && !visited[v]) return false;

// все вершины с ненулевой степенью были посещены, эйлеров путь существует
return true;
}
47 changes: 47 additions & 0 deletions additional_tasks/find_word_chains/src/find_word_chains.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#pragma once

#include "euler_path.hpp"

/**
* @brief Проверяет, можно ли составить цепочку из заданных слов так, чтобы
* последняя буква каждого слова совпадала с первой буквой следующего.
*
* @param words вектор строк (слов), которые нужно проверить.
*
* @return `true`: слова можно объединить в цепочку
* @return `false`: в противном случае
*/
bool CanBeChained(const std::vector<std::string>& words) {
if (words.empty()) return true;

Graph<char> chars_graph;

for (const std::string& s : words) {
if (s.empty()) continue; // пропускаем пустые строки

chars_graph.AddEdge({s[0], s.back()});
}

// дополнительная проверка на связность:
if (chars_graph.VertsAmount() > 0 && chars_graph.EdgesAmount() == 0)
return false; // есть изолированная вершина

// проверка на Эйлеров цикл
return HasEulerPath(chars_graph);
}

/// @brief Решает задачу: ""
void Solution(std::istream& is = std::cin, std::ostream& os = std::cout) {
size_t words_amount;
is >> words_amount;

std::vector<std::string> words(words_amount);
for (auto& word : words) is >> word;

if (CanBeChained(words))
os << "Yes";
else
os << "No";

os << std::endl;
}
6 changes: 6 additions & 0 deletions additional_tasks/find_word_chains/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "find_word_chains.hpp"

int main() {
Solution();
return 0;
}
93 changes: 93 additions & 0 deletions additional_tasks/find_word_chains/src/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include <gtest/gtest.h>

#include "find_word_chains.hpp"

TEST(CanBeChainedTest, EmptyInput) {
std::vector<std::string> words = {};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, SingleWord) {
std::vector<std::string> words = {"abc"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, SimpleChain) {
std::vector<std::string> words = {"for", "rig", "geek", "kaf"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, SimpleChain2) {
std::vector<std::string> words = {"abc", "cba"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, NoChain2) {
std::vector<std::string> words = {"abc", "def"};
ASSERT_FALSE(CanBeChained(words));
}

TEST(CanBeChainedTest, SameStartEnd) {
std::vector<std::string> words = {"aba", "cba", "acc"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, MultipleSameWords) {
std::vector<std::string> words = {"aba", "aba", "aca"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, WordsWithSameStartEnd) {
std::vector<std::string> words = {"aaa", "bbb", "ccc"};
ASSERT_FALSE(CanBeChained(words));
}

TEST(CanBeChainedTest, MultipleWordsWithSameStartEnd) {
std::vector<std::string> words = {"aaa", "aba", "aca"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, MultipleWordsWithSameStartEnd2) {
std::vector<std::string> words = {"aaa", "abb", "aba"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, ComplexChain) {
std::vector<std::string> words = {"aba", "bab", "bba"};
ASSERT_FALSE(CanBeChained(words));
}

TEST(CanBeChainedTest, ComplexChain2) {
std::vector<std::string> words = {"abc", "cde", "efg", "gba"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, ComplexNoChain) {
std::vector<std::string> words = {"abc", "cde", "efg", "hbl"};
ASSERT_FALSE(CanBeChained(words));
}

TEST(CanBeChainedTest, OneWordMultipleSameLetters) {
std::vector<std::string> words = {"aaaa"};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, TwoWordsSameStartEnd) {
std::vector<std::string> words = {"aaa", "bbb"};
ASSERT_FALSE(CanBeChained(words));
}

TEST(CanBeChainedTest, EmptyWords) {
std::vector<std::string> words = {"abc", "", "def"};
ASSERT_FALSE(CanBeChained(words));
}

TEST(CanBeChainedTest, EmptyWordsOnly) {
std::vector<std::string> words = {"", "", ""};
ASSERT_TRUE(CanBeChained(words));
}

TEST(CanBeChainedTest, LongChain) {
std::vector<std::string> words = {"a", "ab", "bc", "cd", "de", "ef", "fa"};
ASSERT_TRUE(CanBeChained(words));
}
39 changes: 39 additions & 0 deletions additional_tasks/graph/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
Utils
)

include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME}_tests)
5 changes: 5 additions & 0 deletions additional_tasks/graph/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Класс `Graph` от Криворучко Дмитрия Игоревича

Ненормальный класс `Graph`, содержащий класс `Edge` в качестве приватного поля, но не имеющий отдельного класса Vertice (допускается использование только `std::string` и целочисленных типов (`int`, `short`, `std::size_t`) в качестве названия вершин, подставляемых шаблонами) с восемью статическими методами — псевдоконструкторами, различными вспомогательными методами для удаления и поиска ребра или вершины.

Может использоваться для решения специфических задач, где граф надо хранить не как список смежности, а как списки вершин и рёбер.
175 changes: 175 additions & 0 deletions additional_tasks/graph/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#include <iostream>

#include "graph.hpp"

template <typename V, typename W>
void PrintGraph(const Graph<V, W>& graph) {
std::cout << std::endl << graph << std::endl << std::endl;
graph.PrintAdjList();
std::cout << std::endl << graph.GetAdjList() << std::endl;
}

int main() {
// ------------------------- Graph from pairs -------------------------

std::cout << std::endl
<< "------------------------------"
" Graph from pairs "
"------------------------------"
<< std::endl
<< std::endl;

const std::vector<std::pair<size_t, size_t>> edges_pairs = {
{0, 1}, {1, 2}, {2, 0}};

auto graph = Graph<size_t, long>::GraphNonWeighted(edges_pairs);

std::cout << "Pairs: " << edges_pairs << std::endl;

PrintGraph(graph);

// ------------------- Graph from pairs and weights -------------------

std::cout << std::endl
<< "------------------------------"
" Graph from pairs and weights "
"------------------------------"
<< std::endl
<< std::endl;

const std::vector<long> weights = {1, 2, 3};

graph = Graph<size_t, long>::GraphWeighted(edges_pairs, weights);

std::cout << "Pairs: " << edges_pairs << std::endl
<< "Weights: " << weights << std::endl;

PrintGraph(graph);

// ------------------------- Graph from tuples ------------------------

std::cout << std::endl
<< "------------------------------"
" Graph from tuples "
"------------------------------"
<< std::endl
<< std::endl;

const std::vector<std::tuple<size_t, size_t, long>> edges_tuples = {
{0, 1, 5}, {1, 2, 10}, {2, 0, 3}};

graph = Graph<size_t, long>::GraphWeighted(edges_tuples);

std::cout << "Tuples: " << edges_tuples << std::endl;

PrintGraph(graph);

// ------------------------ Graph from strings ------------------------

std::cout << std::endl
<< "------------------------------"
" Graph from strings "
"------------------------------"
<< std::endl
<< std::endl;

const std::vector<std::string> edges_strs = {"0->1", "2->1", "3->2", "1->3"};

graph = Graph<size_t, long>::GraphFromStrs(edges_strs);

std::cout << "Strings: " << edges_strs << std::endl;

PrintGraph(graph);

// -------------------------- Graph from dict -------------------------

std::cout << std::endl
<< "------------------------------"
" Graph from dict "
"------------------------------"
<< std::endl
<< std::endl;

const std::unordered_map<std::string, long> edges_dict = {
{"0->1", 5}, {"2->1", 1}, {"3->2", 2}, {"1->3", 3}};

graph = Graph<size_t, long>::GraphFromMap(edges_dict);

std::cout << "Dicts: " << edges_dict << std::endl;

PrintGraph(graph);

// ----------------------- Graph from AdjMatrix -----------------------

std::cout << std::endl
<< "------------------------------"
" Graph from AdjMatrix "
"------------------------------"
<< std::endl
<< std::endl;

std::vector<std::vector<long>> adj_matrix = {{0, 1, 0}, {1, 0, 1}, {0, 1, 0}};

graph = Graph<size_t, long>::GraphFromAdjMatrix(adj_matrix);

std::cout << "AdjMatrix: " << adj_matrix << std::endl;

PrintGraph(graph);

std::cout << std::endl << "WEIGHTED VERSION:" << std::endl << std::endl;

adj_matrix = {{0, 1, 0}, {1, 0, 5}, {0, 7, 0}};
graph = Graph<size_t, long>::GraphFromAdjMatrix(adj_matrix, true);

std::cout << "AdjMatrix: " << adj_matrix << std::endl;

PrintGraph(graph);

// ------------------------ Graph from AdjList ------------------------

std::cout << std::endl
<< "------------------------------"
" Graph from AdjList "
"------------------------------"
<< std::endl
<< std::endl;

const std::vector<std::vector<size_t>> adj_list = {{1}, {0, 2}, {1}};

graph = Graph<size_t, long>::GraphFromAdjList(adj_list);

std::cout << "AdjList: " << adj_list << std::endl;

PrintGraph(graph);

// ------------------- Graph from AdjList with keys -------------------

std::cout << std::endl
<< "------------------------------"
" Graph from AdjList with keys "
"------------------------------"
<< std::endl
<< std::endl;

const std::unordered_map<size_t, std::vector<size_t>> adj_list_dict = {
{0, {1}}, {1, {0, 2}}, {2, {1}}};

graph = Graph<size_t, long>::GraphFromAdjList(adj_list_dict);

std::cout << "AdjList: " << adj_list_dict << std::endl;

PrintGraph(graph);

std::cout << std::endl << "STRING VERSION:" << std::endl << std::endl;

std::unordered_map<std::string, std::vector<std::string>> adj_list_dict_2 = {
{"A", {"B"}}, {"B", {"A", "C"}}, {"C", {"B"}}};

auto graph_2 = Graph<std::string, long>::GraphFromAdjList(adj_list_dict_2);

std::cout << "AdjList: " << adj_list_dict_2 << std::endl;

PrintGraph(graph_2);

return 0;
}
540 changes: 540 additions & 0 deletions additional_tasks/graph/src/test.cpp

Large diffs are not rendered by default.

39 changes: 39 additions & 0 deletions additional_tasks/petya_and_vasya_labyrinth/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
Utils
)

include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME}_tests)
218 changes: 218 additions & 0 deletions additional_tasks/petya_and_vasya_labyrinth/README.md

Large diffs are not rendered by default.

95 changes: 95 additions & 0 deletions additional_tasks/petya_and_vasya_labyrinth/src/a_star.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#pragma once

#include <functional>
#include <limits>
#include <queue>
#include <set>

#include "graph.hpp"

/**
* @brief Вычисляет кратчайший путь между двумя вершинами с помощью A*.
* @tparam vert_t: тип вершины в графе
* @tparam weight_t: тип веса в графе
* @param start: начальная вершина.
* @param goal: конечная вершина.
* @param graph: граф, для которого необходимо вычислить кратчайшие пути.
* @param heuristic_range: функция эвристической оценки расстояния от
* произвольной вершины до конечной вершины. Должна принимать две вершины в
* качестве аргументов и возвращать вес (оценку расстояния).
* @throw `std::invalid_argument("AStar: there is no such start vertice in
* graph.")`.
* @throw `std::invalid_argument("AStar: there is no such goal vertice in
* graph.")`.
* @return `weight_t`: стоимость кратчайшего пути от `start` до `goal`
* (если до вершины нет пути, то значение будет равно
* `std::numeric_limits<weight_t>::max()`).
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
weight_t AStar(
const vert_t& start, const vert_t& goal,
const Graph<vert_t, weight_t>& graph,
std::function<weight_t(const vert_t&, const vert_t&)> heuristic_range) {
if (!graph.ContainsVert(start))
throw std::invalid_argument(
"AStar: there is no such start vertice in graph.");

if (!graph.ContainsVert(goal))
throw std::invalid_argument(
"AStar: there is no such goal vertice in graph.");

std::priority_queue<std::pair<weight_t, vert_t>,
std::vector<std::pair<weight_t, vert_t>>,
std::greater<std::pair<weight_t, vert_t>>>
/**
* @brief приоритетная очередь для вершин, которые нужно посетить
* @details пары (стоимость, вершина), упорядоченные по стоимости.
*/
visited_verts;

// @brief стоимости пути от стартовой вершины до каждой вершины
std::unordered_map<vert_t, weight_t> cost_from_start;

// @brief суммы стоимостей пути от стартовой вершины и эвристики до конечной
std::unordered_map<vert_t, weight_t> range_plus_cost;

for (const auto& v : graph.Verts())
cost_from_start[v] = std::numeric_limits<weight_t>::max();

// начальные значения
cost_from_start[start] = 0;
range_plus_cost[start] =
cost_from_start[start] + heuristic_range(start, goal);
visited_verts.push({range_plus_cost[start], start});

while (!visited_verts.empty()) {
// посещенная вершина с минимальным значением range_plus_cost
const vert_t current = visited_verts.top().second;
visited_verts.pop();

// достигнута конечная вершина, уже нашли путь до нужной вершины
if (current == goal) return cost_from_start[goal];

const auto neighbors = graph.GetAdjList()[current];
for (const auto& neighbor : neighbors) {
// @brief предполагаемая стоимость пути до соседа
weight_t tentative_score =
cost_from_start[current] +
((graph.IsWeighted()) ? graph.GetEdgeWeight({current, neighbor}) : 1);

// предполагаемая стоимость меньше текущей,
// обновляем стоимость и добавляем соседа в очередь
if (tentative_score < cost_from_start[neighbor]) {
cost_from_start[neighbor] = tentative_score;

// range_plus_cost для новой вершины (другого соседа)
range_plus_cost[neighbor] =
cost_from_start[neighbor] + heuristic_range(neighbor, goal);
visited_verts.push({range_plus_cost[neighbor], neighbor});
}
}
}

// путь не был найден
return std::numeric_limits<weight_t>::max();
}
61 changes: 61 additions & 0 deletions additional_tasks/petya_and_vasya_labyrinth/src/dag_relaxation.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include <limits>

#include "topological_sort.hpp"

/**
* @brief Вычисляет кратчайшие пути от заданной начальной вершины до всех других
* вершин в направленном ациклическом графе (DAG) с помощью релаксации.
* @tparam vert_t: тип вершины в графе.
* @tparam weight_t: тип веса в графе.
* @param graph: граф, для которого необходимо вычислить кратчайшие пути.
* @param start: начальная вершина, от которой вычисляются расстояния.
* @throw `std::invalid_argument("DAGRelaxation: there is no such start
* vertice.")`.
* @throw `std::invalid_argument("DAGRelaxation: graph is not directed.")`.
* @return `std::unordered_map<vert_t, weight_t>`: словарь, где ключ - вершина,
* а значение - кратчайшее расстояние от start до этой вершины (если до вершины
* нет пути, то значение будет равно `std::numeric_limits<weight_t>::max()`).
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
std::unordered_map<vert_t, weight_t> DAGRelaxation(
const Graph<vert_t, weight_t>& graph, const vert_t& start) {
if (!graph.ContainsVert(start))
throw std::invalid_argument(
"DAGRelaxation: there is no such start vertice in graph.");

if (!graph.IsDirected())
throw std::invalid_argument("DAGRelaxation: graph is not directed.");

/// @brief хеш-таблица расстояний от start до каждой вершины
std::unordered_map<vert_t, weight_t> dists;

// инициализация расстояний от start до каждой вершины бесконечностями
for (const auto& vert : graph.Verts())
dists[vert] = std::numeric_limits<weight_t>::max();

// расстояние от начальной вершины до самой себя равно 0
dists[start] = 0;

std::vector<vert_t> sorted_verts = TopologicalSort(graph);

for (const auto& u_vert : sorted_verts)
for (size_t i = 0; i < graph.GetAdjList()[u_vert].size(); i++) {
auto vert = graph.GetAdjList()[u_vert][i];

// (нас не интересует бесконечное расстояние от start до u_vert)
if (dists[u_vert] == std::numeric_limits<weight_t>::max()) continue;

// если граф не взвешен, то задаем расстояние единицей
weight_t u_v_dist = 1;
if (graph.IsWeighted()) u_v_dist = graph.GetEdgeWeight({u_vert, vert});

// релаксируем ребро, если текущее расстояние до vert больше, чем
// расстояние до u_vert + расстояние между вершинами (вес их ребра)
if (dists[vert] > dists[u_vert] + u_v_dist)
dists[vert] = dists[u_vert] + u_v_dist;
}

return dists;
}
7 changes: 7 additions & 0 deletions additional_tasks/petya_and_vasya_labyrinth/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "petya_and_vasya_labyrinth.hpp"

int main() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: an exception may be thrown in function 'main' which should not throw exceptions [bugprone-exception-escape]

int main() {
    ^

// ComplicatedBadSolution();
GoodSolution();
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
#include <algorithm>
#include <fstream>
#include <sstream>
#include <stack>

#include "a_star.hpp"
#include "dag_relaxation.hpp"

namespace {

/**
* @brief Находит ячейку в заданном векторе строк, соответствующую определенному
* суффиксу.
* @param strings: вектор строк для поиска.
* @param i: индекс.
* @param j: индекс.
* @return std::string: строка, содержащая суффикс, или пустая строка, если
* совпадений не найдено.
*/
inline std::string FindCell(const std::vector<std::string>& strings, size_t i,
size_t j) {
std::string suffix = "_" + std::to_string(i) + "_" + std::to_string(j);

auto it =
std::find_if(strings.begin(), strings.end(), [&](const std::string& str) {
return str.find(suffix) != std::string::npos;
});

return it == strings.end() ? "" : *it;
}

/**
* @brief Добавляет ребра в граф на основе смежности ячеек.
* @param graph: граф, в который нужно добавить ребра (НЕНАПРАВЛЕННЫЙ).
*/
inline void AddEdgesNonDirected(Graph<std::string, long>& graph) {
for (const auto& vert : graph.Verts()) {
size_t j = std::stoll(vert.substr(vert.rfind('_') + 1));
size_t i = std::stoll(vert.substr(
vert.rfind('_', vert.rfind('_') - 1) + 1,
vert.rfind('_') - vert.rfind('_', vert.rfind('_') - 1) - 1));

std::string up_vert = "", down_vert = "";
auto right_vert = FindCell(graph.Verts(), i + 1, j);
auto left_vert = FindCell(graph.Verts(), i, j + 1);

if (i != 0 && j != 0) {
up_vert = FindCell(graph.Verts(), i - 1, j);
down_vert = FindCell(graph.Verts(), i, j - 1);
}

for (const auto& another_vert : {up_vert, down_vert, right_vert, left_vert})
if (!another_vert.empty() && !graph.ContainsEdge({another_vert, vert}))
graph.AddEdge({vert, another_vert});
}
}

/**
* @brief Итеративно добавляет ребра в граф на основе смежности ячеек.
* @param graph: граф, в который нужно добавить ребра (НАПРАВЛЕННЫЙ).
* @param vert: начальная вершина для обхода.
*/
inline void AddEdgesIterative(Graph<std::string, long>& graph,
const std::string& vert) {
std::stack<std::string> s;
s.push(vert);

while (!s.empty()) {
std::string current_vert = s.top();
s.pop();

size_t j = std::stoll(current_vert.substr(current_vert.rfind('_') + 1));
size_t i = std::stoll(current_vert.substr(
current_vert.rfind('_', current_vert.rfind('_') - 1) + 1,
current_vert.rfind('_') -
current_vert.rfind('_', current_vert.rfind('_') - 1) - 1));

std::string up_vert = "", down_vert = "";
auto right_vert = FindCell(graph.Verts(), i + 1, j);
auto left_vert = FindCell(graph.Verts(), i, j + 1);

if (i != 0 && j != 0) {
up_vert = FindCell(graph.Verts(), i - 1, j);
down_vert = FindCell(graph.Verts(), i, j - 1);
}

for (const auto& another_vert : {up_vert, down_vert, right_vert, left_vert})
if (!another_vert.empty() &&
!graph.ContainsEdge({another_vert, current_vert})) {
graph.AddEdge({current_vert, another_vert});
if (another_vert[0] != 'E') s.push(another_vert);
}
}
}

} // namespace

struct PetyaAndVasyaGraphs {
struct {
Graph<std::string, long> graph;
std::string vert;
std::vector<std::string> exits;
} petya, vasya;
};

enum class SolutionType { ComplicatedBad, Good };

/**
* @brief Разбирает входные данные лабиринта из стандартного ввода и создает
* направленный граф.
* @return `PetyaAndVasyaGraphs`, содержащий графы для обоих игроков.
*/
inline PetyaAndVasyaGraphs ParseMaze(
std::istream& is = std::cin,
SolutionType sol_type = SolutionType::ComplicatedBad) {
Graph<std::string, long> graph;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: enum 'SolutionType' uses a larger base type ('int', size: 4 bytes) than necessary for its value set, consider using 'std::uint8_t' (1 byte) as the base type to reduce its size [performance-enum-size]

a;
                  ^

std::vector<std::string> console_maze;
std::string line;

PetyaAndVasyaGraphs maze;
size_t petya_start_i = -1, petya_start_j = -1, vasya_start_i = -1,
vasya_start_j = -1;

// считывание лабиринта
while (std::getline(is, line) && !line.empty()) console_maze.push_back(line);

// добавление вершин
for (size_t i = 0; i < console_maze.size(); i++) {
const auto& line = console_maze[i];

for (size_t j = 0; j < line.size(); j++) {
const auto& cell = line[j];
std::stringstream cell_name_stream;

if (cell == 'P') {
petya_start_i = i, petya_start_j = j;
maze.petya.vert = "E_" + std::to_string(i) + "_" + std::to_string(j);
maze.vasya.exits.push_back(maze.petya.vert);

cell_name_stream << 'E';

} else if (cell == 'V') {
vasya_start_i = i, vasya_start_j = j;
maze.vasya.vert = "E_" + std::to_string(i) + "_" + std::to_string(j);
maze.petya.exits.push_back(maze.vasya.vert);

cell_name_stream << 'E';
}

else if (cell == '0') {
if (j == 0 || i == 0 || j == (line.size() - 1) ||
i == (console_maze.size() - 1)) {
auto vert = "E_" + std::to_string(i) + "_" + std::to_string(j);

maze.vasya.exits.push_back(vert);
maze.petya.exits.push_back(vert);

cell_name_stream << 'E';
}

else
cell_name_stream << 'C';
}

if (!cell_name_stream.str().empty()) {
cell_name_stream << "_" << i << "_" << j;

graph.AddVert(cell_name_stream.str());
}
}
}

// добавление ребер
maze.petya.graph = graph;
maze.vasya.graph = graph;

switch (sol_type) {
case SolutionType::ComplicatedBad: {
AddEdgesIterative(maze.petya.graph,
FindCell(graph.Verts(), petya_start_i, petya_start_j));

AddEdgesIterative(maze.vasya.graph,
FindCell(graph.Verts(), vasya_start_i, vasya_start_j));

maze.petya.graph.MakeDirected();
maze.vasya.graph.MakeDirected();
break;
}

case SolutionType::Good: {
AddEdgesNonDirected(maze.petya.graph);
maze.petya.graph.MakeUndirected();

maze.vasya.graph = maze.petya.graph;
break;
}
}

return maze;
}

/// @brief Хорошо решает задачу: "Петя и Вася в страшном лабиринте".
void GoodSolution(std::istream& is = std::cin, std::ostream& os = std::cout) {
try {
auto maze = ParseMaze(is, SolutionType::Good);

long petya_min = std::numeric_limits<long>::max();
long vasya_min = std::numeric_limits<long>::max();

auto manhattan_heuristic = [](const std::string& vert_1,
const std::string& vert_2) {
auto extract_coords = [](const std::string& vert) {
size_t j = std::stoll(vert.substr(vert.rfind('_') + 1));
size_t i = std::stoll(vert.substr(
vert.rfind('_', vert.rfind('_') - 1) + 1,
vert.rfind('_') - vert.rfind('_', vert.rfind('_') - 1) - 1));

return std::make_pair(i, j);
};

// в матрице (лабиринте) координаты положительные, модули не нужны
return extract_coords(vert_1).first - extract_coords(vert_2).first +
extract_coords(vert_1).second - extract_coords(vert_2).second;
};

// (размеры будут одинаковые)
for (size_t i = 0; i < maze.petya.exits.size(); i++) {
petya_min = std::min(
petya_min,
AStar(maze.petya.vert, maze.petya.exits[i], maze.petya.graph,
std::function<long(const std::string&, const std::string&)>(
manhattan_heuristic)));

vasya_min = std::min(
vasya_min,
AStar(maze.vasya.vert, maze.vasya.exits[i], maze.vasya.graph,
std::function<long(const std::string&, const std::string&)>(
manhattan_heuristic)));
}

if (vasya_min < petya_min)
os << "Vasya! with " << vasya_min << std::endl;

else if (vasya_min == petya_min)
if (vasya_min != std::numeric_limits<long>::max())
os << "Draw! with " << vasya_min << std::endl;
else
os << "Deadlock! Valery!" << std::endl;

else
os << "Petya! with " << petya_min << std::endl;

} catch (const std::exception&) {
throw std::invalid_argument("Invalid enter format!");
}
}

/// @brief Сложно и плохо решает задачу: "Петя и Вася в страшном лабиринте".
void ComplicatedBadSolution(std::istream& is = std::cin,
std::ostream& os = std::cout) {
try {
auto maze = ParseMaze(is, SolutionType::ComplicatedBad);

auto petya_relax = DAGRelaxation(maze.petya.graph, maze.petya.vert);
auto vasya_relax = DAGRelaxation(maze.vasya.graph, maze.vasya.vert);

long petya_min = std::numeric_limits<long>::max();
long vasya_min = std::numeric_limits<long>::max();

// (размеры будут одинаковые)
for (size_t i = 0; i < maze.petya.exits.size(); i++) {
petya_min = std::min(petya_min, petya_relax[maze.petya.exits[i]]);
vasya_min = std::min(vasya_min, vasya_relax[maze.vasya.exits[i]]);
}

if (vasya_min < petya_min)
os << "Vasya! with " << vasya_min << std::endl;

else if (vasya_min == petya_min)
if (vasya_min != std::numeric_limits<long>::max())
os << "Draw! with " << vasya_min << std::endl;
else
os << "Deadlock! Valery!" << std::endl;

else
os << "Petya! with " << petya_min << std::endl;
} catch (const std::exception&) {
throw std::invalid_argument("Invalid enter format!");
}
}
528 changes: 528 additions & 0 deletions additional_tasks/petya_and_vasya_labyrinth/src/test.cpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include "graph.hpp"

namespace {

template <AllowedVertType vert_t>
void TopologicalSortStep(
const vert_t& u_vert, std::unordered_map<vert_t, bool>& visited,
std::unordered_map<vert_t, std::vector<vert_t>>& adj_list,
std::vector<vert_t>& topological_order) {
visited[u_vert] = true;

for (const auto& vert : adj_list[u_vert])
if (!visited[vert])
TopologicalSortStep(vert, visited, adj_list, topological_order);

topological_order.push_back(u_vert);
}

} // namespace

/**
* @brief Производит топологическую сортировку вершин на основе обхода в глубину
* (DFS)
* @tparam vert_t: тип вершины в графе
* @tparam weight_t: тип веса в графе
* @param graph: сортируемый граф
* @throw `std::invalid_argument("TopologicalSort: graph is not directed.")`.
* @return `std::vector<vert_t>`: список отсортированных вершин
*/
template <AllowedVertType vert_t, AllowedWeightType weight_t>
std::vector<vert_t> TopologicalSort(const Graph<vert_t, weight_t>& graph) {
if (!graph.IsDirected())
throw std::invalid_argument("TopologicalSort: graph is not directed.");
if (graph.Verts().empty()) return {};

std::vector<vert_t> topological_order;

std::unordered_map<vert_t, bool> visited;
for (const auto& vert : graph.Verts()) visited[vert] = false;

std::unordered_map<vert_t, std::vector<vert_t>> adj_list = graph.GetAdjList();

for (const auto& vert : graph.Verts())
if (!visited[vert])
TopologicalSortStep(vert, visited, adj_list, topological_order);

std::reverse(topological_order.begin(), topological_order.end());

return topological_order;
}
25 changes: 12 additions & 13 deletions additional_tasks/template_task/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} C CXX)
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE lib_source_list "../../lib/src/*.cpp" "../../lib/src/*.hpp")
file(GLOB_RECURSE main_source_list "src/main.cpp")
file(GLOB_RECURSE test_source_list "src/*.cpp")
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM test_source_list ${main_source_list})
list(REMOVE_ITEM source_list ${test_list})
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

add_executable(${PROJECT_NAME} ${source_list} ${lib_source_list})
find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

find_library(Utils ../)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${test_source_list})
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
6 changes: 4 additions & 2 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
cmake_minimum_required(VERSION 3.10)
project(Utils)
cmake_minimum_required(VERSION 3.20)

project(Utils LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)

file(GLOB_RECURSE lib_source_list "src/*.cpp" "src/*.hpp")

add_library(${PROJECT_NAME} ${lib_source_list})

set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src)
873 changes: 873 additions & 0 deletions lib/src/graph.hpp

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion lib/src/util.cpp

This file was deleted.

Empty file removed lib/src/util.hpp
Empty file.
179 changes: 179 additions & 0 deletions lib/src/utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
#pragma once

// std libs:
#include <algorithm>
#include <iostream>
#include <numeric>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>

using std::size_t;

/**
* @brief Выводит все элементы пары в поток
* @tparam Type1: тип, возможный к выводу в консоль
* @tparam Type2: тип, возможный к выводу в консоль
* @param os: ссылка на поток, в который надо вывести (мод.)
* @param pair: пара элементов произвольного типа
* @return std::ostream&: ссылка на поток, в который вывели
*/
template <typename Type1, typename Type2>
inline std::ostream& operator<<(std::ostream& os,
const std::pair<Type1, Type2>& pair) {
return os << "{" << pair.first << "; " << pair.second << "}";
}

/**
* @brief Выводит все элементы std::tuple в поток
* @tparam I: текущий индекс, обрабатываемый в кортеже
* @tparam Ts: типы элементов в кортеже
* @param os: выходной поток, в который будут записаны элементы кортежа
* @param t: кортеж, который нужно распечатать
* @return std::ostream&: модифицированный выходной поток
*/
template <std::size_t I = 0, typename... Ts>
static std::ostream& PrintTuple(std::ostream& os, const std::tuple<Ts...>& t) {
if constexpr (I < sizeof...(Ts)) {
if (I != 0) os << "; ";

os << std::get<I>(t);
return PrintTuple<I + 1, Ts...>(os, t);
} else
return os;
}

/**
* @brief Выводит все элементы std::tuple в поток
* @tparam Ts: типы элементов в кортеже.
* @param os: выходной поток, в который будет записан кортеж.
* @param t: кортеж, который нужно распечатать.
* @return std::ostream&: модифицированный выходной поток
*/
template <typename... Ts>
std::ostream& operator<<(std::ostream& os, const std::tuple<Ts...>& t) {
os << "{";
PrintTuple(os, t);
return os << "}";
}

/**
* @brief Выводит все элементы вектора в поток
* @tparam Type: тип, возможный к выводу в консоль
* @param os: ссылка на поток, в который надо вывести (мод.)
* @param vec: вектор элементов произвольного типа
* @return std::ostream&: ссылка на поток, в который вывели
*/
template <typename Type>
inline std::ostream& operator<<(std::ostream& os,
const std::vector<Type>& vec) {
os << "{";

for (std::size_t i = 0; i < vec.size(); i++) {
os << vec[i];
if (i != vec.size() - 1) os << "; ";
}

return os << "}";
}

/**
* @brief Выводит все элементы std::unordered_map в выходной поток
* @tparam K: тип ключей в неупорядоченной карте
* @tparam V: тип значений в неупорядоченной карте
* @param os: выходной поток, в который будет записан словарь
* @param map: словарь, который нужно распечатать
* @return std::ostream&: модифицированный выходной поток
*/
template <typename K, typename V>
std::ostream& operator<<(std::ostream& os,
const std::unordered_map<K, V>& map) {
os << "{";

bool first = true;
for (const auto& [key, value] : map) {
if (!first) os << "; ";

os << key << ": " << value;
first = false;
}

return os << "}";
}

/**
* @brief функция, которая обрезает незнач. нули float при преобр. в строку
* @param number: число типа float
* @return std::string: итоговое число, записанное в строку
*/
inline std::string ErasedZerosStr(float number) {
std::string str = std::to_string(number);

// удаляем незначащие нули
str.erase(str.find_last_not_of('0') + 1, std::string::npos);

// если последний символ - десятичная точка, удаляем
if (str.back() == '.') str.pop_back();

return str;
}

/**
* @brief перегрузка, которая вводит все элементы вектора из потока
* (работает исключительно с консолью, так как
* (вывод о текущем состоянии происходит туда)
* @tparam Type: тип, возможный к выводу в консоль
* @param is: ссылка на поток, из которого надо ввести (мод.)
* @param vec: вектор элементов типа Type (мод.)
* @return std::istream&: ссылка на поток, из которого ввели
*/
template <typename Type>
inline std::istream& operator>>(std::istream& is, std::vector<Type>& vec) {
// @brief размер вектора
long size = 0;

std::cout << "Enter array size: ";
while (size <= 0) {
is >> size;
if (!is) {
std::cerr << "Invalid size input." << std::endl;
return is;
}

if (size <= 0) std::cout << "Invalid size input. Try again: ";
}

// @brief текущий элемент
Type curr;

vec.clear(); // (для перезаписи нужна отчистка)
std::cout << "Enter array elements: ";
for (std::size_t i = 0; i < std::size_t(size); i++) {
is >> curr;
if (!is) {
std::cerr << "Invalid array input. The entry is incorrect." << std::endl;
return is;
}

vec.push_back(curr);
}

return is;
}

/**
* @brief Проверяет наличие элемента в векторе
* @tparam T: тип элемента
* @param vec: исходный вектор
* @param value: искомое значение
* @return true: элемент найден
* @return false: элемент не найден
*/
template <typename T>
inline bool Contains(const std::vector<T>& vec, const T& value) {
return std::find(vec.begin(), vec.end(), value) != vec.end();
}
20 changes: 9 additions & 11 deletions sandbox/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

project(sendbox)
project(sandbox LANGUAGES CXX)

file(GLOB_RECURSE tasks_dirs LIST_DIRECTORIES true ".")
file(GLOB_RECURSE SUBFOLDERS LIST_DIRECTORIES true ".")

foreach(dir ${tasks_dirs})
IF(IS_DIRECTORY ${dir})
IF(NOT ${dir} MATCHES ".*src.*")
add_subdirectory(${dir})
ENDIF()
ELSE()
CONTINUE()
ENDIF()
foreach(FOLDER ${SUBFOLDERS})
if(IS_DIRECTORY ${FOLDER})
if(NOT ${FOLDER} MATCHES ".*src.*")
add_subdirectory(${FOLDER})
endif()
endif()
endforeach()
25 changes: 12 additions & 13 deletions sandbox/template/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} C CXX)
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE main_source_list "src/main.cpp")
file(GLOB_RECURSE test_source_list "src/*.cpp")
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM test_source_list ${main_source_list})
list(REMOVE_ITEM source_list ${test_list})
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

add_executable(${PROJECT_NAME} ${source_list})
find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

find_library(Utils ../)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${test_source_list})

add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
25 changes: 12 additions & 13 deletions task_01/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} C CXX)
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE main_source_list "src/main.cpp")
file(GLOB_RECURSE test_source_list "src/*.cpp")
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM test_source_list ${main_source_list})
list(REMOVE_ITEM source_list ${test_list})
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

add_executable(${PROJECT_NAME} ${source_list})
find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)

include_directories(${GTEST_INCLUDE_DIRS})

find_library(Utils ../)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${test_source_list})
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
24 changes: 12 additions & 12 deletions task_02/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} C CXX)
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE main_source_list "src/main.cpp")
file(GLOB_RECURSE test_source_list "src/*.cpp")
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM test_source_list ${main_source_list})
list(REMOVE_ITEM source_list ${test_list})
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

add_executable(${PROJECT_NAME} ${source_list})
find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

find_library(Utils ../)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${test_source_list})
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
24 changes: 12 additions & 12 deletions task_03/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} C CXX)
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE main_source_list "src/main.cpp")
file(GLOB_RECURSE test_source_list "src/*.cpp")
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM test_source_list ${main_source_list})
list(REMOVE_ITEM source_list ${test_list})
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

add_executable(${PROJECT_NAME} ${source_list})
find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

find_library(Utils ../)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${test_source_list})
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
24 changes: 12 additions & 12 deletions task_04/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} C CXX)
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE main_source_list "src/main.cpp")
file(GLOB_RECURSE test_source_list "src/*.cpp")
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM test_source_list ${main_source_list})
list(REMOVE_ITEM source_list ${test_list})
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

add_executable(${PROJECT_NAME} ${source_list})
find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

find_library(Utils ../)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${test_source_list})
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
24 changes: 12 additions & 12 deletions task_05/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} C CXX)
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE main_source_list "src/main.cpp")
file(GLOB_RECURSE test_source_list "src/*.cpp")
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM test_source_list ${main_source_list})
list(REMOVE_ITEM source_list ${test_list})
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

add_executable(${PROJECT_NAME} ${source_list})
find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

find_library(Utils ../)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${test_source_list})
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main
24 changes: 12 additions & 12 deletions task_06/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.20)

get_filename_component(PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
string(REPLACE " " "_" PROJECT_NAME ${PROJECT_NAME})
project(${PROJECT_NAME} C CXX)
project(${PROJECT_NAME} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB_RECURSE source_list "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE main_source_list "src/main.cpp")
file(GLOB_RECURSE test_source_list "src/*.cpp")
file(GLOB_RECURSE SOURCE_LIST "src/*.cpp" "src/*.hpp")
file(GLOB_RECURSE MAIN_SOURCE_LIST "src/main.cpp")
set(TEST_SOURCE_LIST ${SOURCE_LIST})
file(GLOB_RECURSE test_list "src/*test.cpp")

list(REMOVE_ITEM test_source_list ${main_source_list})
list(REMOVE_ITEM source_list ${test_list})
list(REMOVE_ITEM TEST_SOURCE_LIST ${MAIN_SOURCE_LIST})
list(REMOVE_ITEM SOURCE_LIST ${test_list})

include_directories(${PROJECT_NAME} PUBLIC src)

add_executable(${PROJECT_NAME} ${source_list})
find_library(Utils ../)

add_executable(${PROJECT_NAME} ${SOURCE_LIST})
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Locate GTest
enable_testing()
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

find_library(Utils ../)
target_link_libraries(${PROJECT_NAME} PUBLIC Utils)

# Link runTests with what we want to test and the GTest and pthread library
add_executable(${PROJECT_NAME}_tests ${test_source_list})
add_executable(${PROJECT_NAME}_tests ${TEST_SOURCE_LIST})
target_link_libraries(
${PROJECT_NAME}_tests
GTest::gtest_main