diff --git a/.github/workflows/check-solutions.yaml b/.github/workflows/check-solutions.yaml index 373681f6..faa9b088 100644 --- a/.github/workflows/check-solutions.yaml +++ b/.github/workflows/check-solutions.yaml @@ -32,11 +32,3 @@ jobs: - name: Check code format run: | ruff format --check ./solutions ./homeworks - - - name: Test code - run: | - pytest -v --cov=./solutions ./tests/ - - - name: Test code hw - run: | - pytest -v --cov=./homeworks ./tests_hw/ diff --git a/conditions/lesson04/tasks.md b/conditions/sem01/lesson04/tasks.md similarity index 95% rename from conditions/lesson04/tasks.md rename to conditions/sem01/lesson04/tasks.md index 1858a622..b719b179 100644 --- a/conditions/lesson04/tasks.md +++ b/conditions/sem01/lesson04/tasks.md @@ -2,7 +2,7 @@ **Условие:** Дан список целых чисел, который может быть не отсортирован. Требуется определить, можно ли расположить элементы этого списка в таком порядке, чтобы они образовывали арифметическую прогрессию. Арифметическая прогрессия — это последовательность чисел, в которой разность между любыми двумя соседними элементами постоянна. Например, последовательности `[1, 3, 5, 7]` и `[10, 5, 0, -5]` являются арифметическими прогрессиями, а `[1, 2, 4]` — нет. -Допишите код в файле [task1](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task1.py). +Допишите код в файле [task1](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson04/task1.py). **Входные данные:** - `lst` — список целых чисел: @@ -28,7 +28,7 @@ Дан список отрезков на числовой прямой. Каждый отрезок задаётся парой целых чисел — его левой и правой границей (включительно). Требуется объединить все пересекающиеся или касающиеся отрезки и вернуть список непересекающихся отрезков, покрывающих ту же область. -Допишите код в файле [task2](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task2.py). +Допишите код в файле [task2](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson04/task2.py). **Входные данные:** - `intervals` — список списков целых чисел: @@ -57,7 +57,7 @@ Ваша задача — найти этого самопровозглашённого индивидуалиста, используя минимально возможную память и ровно один проход по данным. Решение должно работать за O(N) по времени и O(1) по памяти — потому что даже уникальность не отменяет законы эффективности. -Допишите код в файле [task3](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task3.py). +Допишите код в файле [task3](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson04/task3.py). **Входные данные:** - `nums` — список целых чисел: @@ -82,7 +82,7 @@ После перестановки вам нужно сообщить, **с какого индекса начинается «нулевая зона»** — это поможет другим модулям знать, где заканчиваются полезные данные. -Допишите код в файле [task4](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task4.py). +Допишите код в файле [task4](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson04/task4.py). **Входные данные:** - `nums` — список целых чисел: @@ -113,7 +113,7 @@ И чтобы не тратить драгоценное время на перебор всей матрицы, нужно сделать это **максимально эффективно** — за **O(N + M)**, где `N` — число серверов, `M` — число временных интервалов. -Допишите код в файле [task5](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task5.py). +Допишите код в файле [task5](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson04/task5.py). **Входные данные:** - `matrix` — список списков целых чисел: @@ -140,7 +140,7 @@ Ваша задача — определить, **сколько всего независимых циклов** спрятано в этом массиве. -Допишите код в файле [task6](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson04/task6.py). +Допишите код в файле [task6](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson04/task6.py). **Входные данные:** - `arr` — список целых чисел: diff --git a/conditions/lesson05/tasks.md b/conditions/sem01/lesson05/tasks.md similarity index 96% rename from conditions/lesson05/tasks.md rename to conditions/sem01/lesson05/tasks.md index 148654ae..a608c0dd 100644 --- a/conditions/lesson05/tasks.md +++ b/conditions/sem01/lesson05/tasks.md @@ -5,7 +5,7 @@ Ваша задача — определить, является ли заданная строка палиндромом. -Допишите код в файле [task1](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson05/task1.py). +Допишите код в файле [task1](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson05/task1.py). **Входные данные:** - `text` — строка, состоящая из букв английского алфавита в верхнем и нижнем регистре длиной от 0 до 1000. @@ -28,7 +28,7 @@ Ваша задача — определить, являются ли два заданных слова анаграммами. Решение должно работать за **O(N + M)** времени и использовать не более **O(1)** дополнительной памяти (где **N** и **M** — длины слов). -Допишите код в файле [task2](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson05/task2.py). +Допишите код в файле [task2](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson05/task2.py). **Входные данные:** - `word1` — непустая строка из букв английского алфавита (верхний и нижний регистр), длиной от 1 до 1000; @@ -53,7 +53,7 @@ Пустая строка **не считается** строкой из пунктуации. -Допишите код в файле [task3](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson05/task3.py). +Допишите код в файле [task3](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson05/task3.py). **Входные данные:** - `text` — строка из букв, цифр, пробелов и знаков препинания, длиной от 0 до 1000 символов. @@ -79,7 +79,7 @@ Ваша задача — реализовать **разархиватор**: по заархивированной строке восстановить исходный текст. -Допишите код в файле [task4](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson05/task4.py). +Допишите код в файле [task4](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson05/task4.py). **Входные данные:** - `compress_text` — строка, содержащая токены, разделённые пробелами, длиной не более 1000. Каждый токен состоит: @@ -116,7 +116,7 @@ *Использование модуля `re` или других инструментов для работы с регулярными выражениями запрещено.* -Допишите код в файле [task5](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson05/task5.py). +Допишите код в файле [task5](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson05/task5.py). **Входные данные:** - `reg_expr` - cтрока, состоящая из букв `d`, `w` и `s` и символов (не буквы и не цифры) - регулярное выражение. Длина строки находится в диапазоне от 0 до 10; @@ -152,7 +152,7 @@ Если изначальный путь поднимается выше корневого каталога, например, `/../` или `/a/../../`, то необходимо вернуть пустую строку. -Допишите код в файле [task6](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson05/task6.py). +Допишите код в файле [task6](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson05/task6.py). **Входные данные:** diff --git a/conditions/lesson06/tasks.md b/conditions/sem01/lesson06/tasks.md similarity index 100% rename from conditions/lesson06/tasks.md rename to conditions/sem01/lesson06/tasks.md diff --git a/conditions/lesson08/tasks.md b/conditions/sem01/lesson08/tasks.md similarity index 97% rename from conditions/lesson08/tasks.md rename to conditions/sem01/lesson08/tasks.md index 978ad8ad..3dd17e15 100644 --- a/conditions/lesson08/tasks.md +++ b/conditions/sem01/lesson08/tasks.md @@ -8,7 +8,7 @@ Ваша задача заключается в том, чтобы реализовать функционал для получения произвольного числа независимых функций `get_avg()`. -Допишите код в файле [task1](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson08/task1.py). +Допишите код в файле [task1](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson08/task1.py). **Входные данные:** @@ -57,7 +57,7 @@ assert math.isclose(get_avg(-2), -1) Ваша задача - реализовать функционал для сбора статистик в виде параметризованного декоратора. -Допишите код в файле [task2](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/lesson08/task2.py). +Допишите код в файле [task2](https://github.com/EvgrafovMichail/python_mipt_dafe_tasks/blob/main/solutions/sem01/lesson08/task2.py). **Входные данные:** diff --git a/conditions/lesson11/task.md b/conditions/sem01/lesson11/task.md similarity index 99% rename from conditions/lesson11/task.md rename to conditions/sem01/lesson11/task.md index e9cb758a..c783b137 100644 --- a/conditions/lesson11/task.md +++ b/conditions/sem01/lesson11/task.md @@ -77,4 +77,4 @@ - Для объектов типа `Vector2D` определена операция вычисления сопряженного вектора. Для вектора `v1` с абсциссой `abs1` и ординатой `ord1` сопряженным вектором будет называться такой вектор `v1`, абсцисса которого `abs2` будет равна `abs1`, а ордината `ord2` будет равна `-ord1`. Операция должна возвращать новый объект типа `Vector2D`. -Помогите К. реализовать задуманный объект. Добавьте свой код в файле [task1](../../solutions/lesson11/task1.py). +Помогите К. реализовать задуманный объект. Добавьте свой код в файле [task1](../../../solutions/sem01/lesson11/task1.py). diff --git a/conditions/lesson12/task.md b/conditions/sem01/lesson12/task.md similarity index 95% rename from conditions/lesson12/task.md rename to conditions/sem01/lesson12/task.md index 3c34e924..4eb2676b 100644 --- a/conditions/lesson12/task.md +++ b/conditions/sem01/lesson12/task.md @@ -10,7 +10,7 @@ - Последний чанк может содержать **меньше элементов**, если размер исходного итерируемого объекта не делится нацело на `size`; - Если исходный итерируемый объект пуст, генератор не должен выдавать ни одного чанка. -Допишите код в файле [task1](../../solutions/lesson12/task1.py). +Допишите код в файле [task1](../../../solutions/sem01/lesson12/task1.py). **Входные данные:** - `iterable` — любой итерируемый объект @@ -40,7 +40,7 @@ tuple(chunked(range(10), 4)) Реализуйте функцию, которая на вход получает итерируемый объект и возвращает циклический генератор. -Допишите код в файле [task2](../../solutions/lesson12/task2.py). +Допишите код в файле [task2](../../../solutions/sem01/lesson12/task2.py). **Входные данные:** @@ -93,7 +93,7 @@ with FileOut("test.txt") as file_manager: sep="\n", ) ``` -Допишите код в файле [task3](../../solutions/lesson12/task3.py). +Допишите код в файле [task3](../../../solutions/sem01/lesson12/task3.py). **Вход**: diff --git a/conditions/sem02/lesson03/tasks.md b/conditions/sem02/lesson03/tasks.md new file mode 100644 index 00000000..31818efb --- /dev/null +++ b/conditions/sem02/lesson03/tasks.md @@ -0,0 +1,144 @@ +## Задача 1: Долой Python! Да здравствует NumPy! + +В этом задании вам будут предложены реализации некоторых функций на Python. Ваша задача - векторизовать код этих функций, используя NumPy. + +### Часть 1. Сложение массивов + +**Условие:** +Векторизуйте код функции `sum_arrays_naive`. + +**Python функция**: +```python +def sum_arrays_naive( + lhs: list[float], + rhs: list[float], +) -> list[float]: + if len(lhs) != len(rhs): + raise ShapeMismatchError + + return [ + elem_lhs + elem_rhs for elem_lhs, elem_rhs in zip(lhs, rhs) + ] +``` + +Допишите код векторизованной функции в файле [task1](../../../solutions/sem02/lesson03/task1.py). + +**Входные данные:** +- `lhs` - одномерный массив чисел с плавающей точкой; +- `rhs` - одномерный массив чисел с плавающей точкой; + +**Выходные данные:** +- Одномерный массив, элементы которого - суммы соответствующих элементов входных массивов. + +**Сторонние эффекты**: +- Если входные массивы `rhs` и `lhs` имеют разное число элементов, необходимо возбудить исключение `ShapeMismatchError`. + +### Часть 2. Полиномы + +Векторизуйте код функции `compute_poly_naive`. + +**Python функция**: +```python +def compute_poly_naive(abscissa: list[float]) -> list[float]: + return [3 * (x ** 2) + 2 * x + 1 for x in abscissa] +``` +Допишите код векторизованной функции в файле [task1](../../../solutions/sem02/lesson03/task1.py). + +**Входные данные:** +- `abscissa` - одномерный массив чисел с плавающей точкой - область определения для вычисления полинома; + +**Выходные данные:** +- Одномерный массив, элементы которого - значения полинома $y = 3 x^2 + 2x + 1$. + +### Часть 3. Далеко ли, близко ли? + +Векторизуйте код функции `get_mutual_l2_distances_naive`. + +Допишите код векторизованной функции в файле [task1](../../../solutions/sem02/lesson03/task1.py). + +**Python функция**: +```python +def get_mutual_l2_distances_naive( + lhs: list[list[float]], + rhs: list[list[float]], +) -> list[list[float]]: + if len(lhs[0]) != len(rhs[0]): + raise ShapeMismatchError + + return [ + [ + sum( + (lhs[i][k] - rhs[j][k]) ** 2 for k in range(len(lhs[0])) + ) ** 0.5 + for j in range(len(rhs)) + ] + for i in range(len(lhs)) + ] +``` + +**Входные данные**: +- `lhs` - двумерный массив чисел с плавающей точкой; +- `rhs` - двумерный массив чисел с плавающей точкой; + +**Выходные данные**: +- Двумерный массив. Элемент `[i][j]` двумерного массива соответствует евклидову расстоянию между `i`-ым вектором из массива `lhs` и `j`-ым вектором из массива `rhs`. Под векторами подразумеваем строки входных двумерных массивов. + +**Сторонние эффекты**: +- Если входные массивы `rhs` и `lhs` имеют разное число колонок, необходимо возбудить исключение `ShapeMismatchError`. + +## Задача 2. Сферические координаты: туда и обратно + +Необходимо реализовать две функции: функцию перевода координат из трехмерной прямоугольной декартовой системы координат в сферические координаты и функцию перевода из сферических координат в трехмерную прямоугольную сферическую систему координат. + +### Перевод из декартовых координат в сферические + +Допишите код функции `convert_from_sphere` в файле [task2](../../../solutions/sem02/lesson03/task2.py). + +**Входные данные**: +- `abscissa` - np.ndarray, абсциссы точек; +- `ordinates` - np.ndarray, ординаты точек; +- `applicates` - np.ndarray, аппликаты точек; + +**Выходные данные**: +- Кортеж (`tuple`) из трех элементов, каждый элемент - np.ndarray. Первый элемент `tuple` - массив расстояний, второй элемент `tuple` - массив углов в диапазоне от $[-\pi, \pi]$ - углы азимута, третий элемент `tuple` - массив углов в диапазоне от $[-\pi, \pi]$ - углы места. + +**Сторонние эффекты**: +- Если количество элементов во входных массивах `abscissa`, `ordinates` и `applicates` отличаются, необходимо возбудить исключение `ShapeMismatchError`. + +*Замечания*: +- Гарантируется, что на вход подаются непустые одномерные массивы чисел с плавающей точкой. +- Предполагаем, что для перевода в сферические координаты используются следующие формулы: + $$x = r*sin(\theta)*cos(\phi); y = r*sin(\theta)*sin(\phi);z = r*cos(\theta)$$ + +### Перевод из сферических координат в декартовы + +Допишите код функции `convert_to_sphere` в файле [task2](../../../solutions/sem02/lesson03/task2.py). + +**Входные данные**: +- `distances` - np.ndarray, массив расстояний; +- `azimuth` - np.ndarray, массив углов азимута в диапазоне $[-\pi, \pi]$; +- `inclination` - np.ndarray, массив углов места в диапазоне $[-\pi, \pi]$ + +**Выходные данные**: +- Кортеж (`tuple`) из трех элементов, каждый элемент - np.ndarray. Первый элемент `tuple` - массив абсцисс, второй элемент `tuple` - массив ординат, третий элемент `tuple` - массив аппликат. + +**Сторонние эффекты**: +- Если количество элементов во входных массивов `distances`, `azimuth` и `inclination` отличаются, необходимо возбудить исключение `ShapeMismatchError`. + +## Задача 3. Низины и возвышенности + +На вход подается одномерный массив чисел с плавающей точкой - значения некоторой функции на определенном отрезке. Ваша задача - вычислить индексы элементов, соответствующие точкам экстремума данной функции. + +Допишите код в файле [task3](../../../solutions/sem02/lesson03/task3.py). + +**Входные данные**: +- `ordinates` - np.ndarray числе с плавающей точкой, значения некоторой функции на определенном отрезке; + +**Выходные данные**: +- Кортеж (`tuple`) из двух элементов. Элементы кортежа - np.ndarray. Первый элемент - индексы точек минимум, второй элемент - индексы точек максимума; + +**Сторонние эффекты**: +- Если в массиве `ordinates` содержится менее трех элементов, необходимо возбудить `ValueError`. + +*Замечение*: +- Краевые точки не принимают участия в вычислениях. Т.е. элементы с индексами 0 и -1 не могут быть точками экстремума. diff --git a/conditions/sem02/lesson04/images/compare.png b/conditions/sem02/lesson04/images/compare.png new file mode 100644 index 00000000..9fed9b26 Binary files /dev/null and b/conditions/sem02/lesson04/images/compare.png differ diff --git a/conditions/sem02/lesson04/images/conv.png b/conditions/sem02/lesson04/images/conv.png new file mode 100644 index 00000000..0b62b034 Binary files /dev/null and b/conditions/sem02/lesson04/images/conv.png differ diff --git a/conditions/sem02/lesson04/images/padding.jpg b/conditions/sem02/lesson04/images/padding.jpg new file mode 100644 index 00000000..65a0f4ec Binary files /dev/null and b/conditions/sem02/lesson04/images/padding.jpg differ diff --git a/conditions/sem02/lesson04/tasks.md b/conditions/sem02/lesson04/tasks.md new file mode 100644 index 00000000..b976407a --- /dev/null +++ b/conditions/sem02/lesson04/tasks.md @@ -0,0 +1,79 @@ +## Задача 1. Нечеткий парень + +В этом задании перед вами стоит задача реализовать фильтр размытия, аналогичный фильтрам размытия из различных редакторов фото. Но прежде чем переходить к реализации самого фильтра размытия, необходимо выполнить подготовительные шаги. + +### Часть 1. Паддингтон + +Слово паддинг (англ. *padding*) буквально можно перевести, как отступ. В контексте текущей задачи паддингом мы будем называть рамку вокруг изображения, шириной в заданное число пикселей, заполненную нулями. На картинке ниже приведен пример добавления паддинга шириной в 1 пиксель к входному изображению. + +![padding](./images/padding.jpg) + +Ваша задача - реализовать функцию добавления паддинга. + +Допишите код функции `pad_image` в файле [task1](../../../solutions/sem02/lesson04/task1.py). + +**Входные данные**: +- `image` - двумерный или трехмерный массив - черно-белое или RGB изображение. Элементы массива - восьмибитные целые беззнаковые числа. +- `pad_size` - натуральное число, ширина паддинга. + +**Выходные данные**: +- Исходное изображение с добавленным паддингом. Т.е. `image`, заключенное в рамку из 0 шириной в `pad_size` пикселей. + +*Сторонние эффекты*: +- Если значение `pad_size` меньше единицы, необходимо возбудить исключение `ValueError`. + +**ВАЖНО**: в это задании запрещено использовать функцию `np.pad`. Решения с использованием `np.pad` будут оценены в 0 баллов! + +### Часть 2. Размытие + +Теперь мы готовы реализовывать фильтр размытия. Размытие изображения работает достаточно просто: + +- Сначала задается размер окна размытия - $l_w$, которое можно интерпретировать, как степень размытия. Чем больше окно размытия, тем сильнее результирующая картинка будет размыта. Обычно в качестве размеров окна размытия используются нечетные целые числа. +- Следующий шаг - это применение паддинга к входному изображению размеров $N \times M$, причем ширина паддинга соответствует следующему выражению: $\lfloor\frac{l_w}{2}\rfloor$. +- Затем, по всему изображению с паддингом запускается обход скользящим окном размеров $l_w \times l_w$, причем центр окна всегда находится в пикселях, соответствующих пикселям исходного изображения. Т.е. центр окна размытия проходит пиксели из области $[l_w, l_w + N] \times [l_w, l_w + M]$ +- В каждом положении окна размытия вычисляется среднее значений пикселей, попавших в окно. Результат записывается в новый массив, тех же размеров, что и исходное изображение. + +![blur](./images/conv.png) + +Ваша задача - реализовать функцию для размытия изображений. + +Допишите код функции `blur_image` в файле [task1](../../../solutions/sem02/lesson04/task1.py). + +**Входные данные**: +- `image` - двумерный или трехмерный массив - черно-белое или RGB изображение. Элементы массива - восьмибитные целые беззнаковые числа. +- `kernel_size` - натуральное нечетное число, размер окна размытия. + +**Выходные данные**: +- Размытое изображение. + +*Сторонние эффекты*: +- Если `kernel_size` четное число или `kernel_size` меньше 1, необходимо возбудить исключение `ValueError`. + +Ожидаемый результат: + +![cirlce](./images/compare.png) + +## Задача 2. Не вижу разницы + +Представим, что вы занимаетесь разработкой некоторого алгоритма дорисовки черно-белых изображений. Для дорисовки используется некоторая модель машинного обучения. Вызов модели занимает продолжительное время, и в некоторых случаях этот вызов не оправдан. Например, дорисовка с помощью модели машинного обучения не оправдана, когда изображение является однотонным (очень много больших областей одного и того же цвета). В этом случае можно было бы использовать цвет однотонных областей для дорисовки требуемых частей изображения. + +Для реализации такого подхода необходимо разработать алгоритм, который позволял бы определить самый распространенный цвет на изображении. Однако есть нюанс. Некоторые цвета черно-белого изображения плохо различимы человеком, и их необходимо рассматривать как один и тот же цвет. Определить плохо различимые цвета можно с помощью критерия `|image[i][j] - image[k][l]| < treshold`. Т.е. если значение разности яркости двух пикселей не превышает заранее заданного порога, то эти пиксели считаются пикселями одного цвета. + +Необходимо реализовать функцию для определения самого распространенного цвета черно-белого изображения с учетом оговоренных особенностей восприятия цвета. Также необходимо рассчитать процент пикселей изображения, окрашенных в самый распространенный цвет, чтобы понимать, возможна ли тривиальная дорисовка или нет. + +Допишите код функции pad_image в файле [task2](../../../solutions/sem02/lesson04/task2.py). + +**Входные данные**: +- `image` - двумерный массив - черно-белое изображение. Элементы массива - восьмибитные целые беззнаковые числа. +- `threshold` - натуральное число, порог для выявления неразличимых цветов. + +**Выходные данные**: +- Кортеж. Первый элемент кортежа - восьмибитное целое беззнаковое число, самый распространенный цвет. Второй элемент кортежа - процент пикселей изображения, окрашенных в самый распространенный цвет. + +*Сторонние эффекты*: +- Если значение `threshold` меньше единицы, необходимо возбудить исключение `ValueError`. + + + + + diff --git a/conditions/sem02/lesson05/tasks.md b/conditions/sem02/lesson05/tasks.md new file mode 100644 index 00000000..e985419a --- /dev/null +++ b/conditions/sem02/lesson05/tasks.md @@ -0,0 +1,80 @@ +## Задача 1. Оптимальное распределение ресурсов + +Некоторая компания производит $N$ типов товаров, используя $M$ типов ресурсов. Для учета производственных стоимостей в компании заведена таблица `costs`, которая представляет собой матрицу размеров $M \times N$. Элемент данной матрицы `costs[i][j]` показывает, сколько единиц i-ого ресурса требуется для производства одной единицы j-ого товара. Для учета доступных ресурсов в компании также определен одномерный массив `resource_amounts`, длины $M$. i-ый элемент массива `resource_amounts` соответствует количеству i-ого ресурса, доступного для использования в производстве. + +Каждый месяц аналитики компании, на основании данных анализа рынка, озвучивают ожидаемый спрос на производимые товары. Ожидаемый спрос характеризуется одномерным массивом `demand_expected`, состоящим из $N$ элементов. i-ому элементу массива `demand_expected` соответствует число товаров, которое необходимо произвести для удовлетворения ожидаемого спроса. + +Ваша задача - разработать функцию, которая позволяет определить, сможет ли компания удовлетворить ожидаемый спрос, учитывая текущее количество ресурсов и текущие производственные стоимости, или нет. + +Допишите код функции `can_satisfy_demand` в файле [task1](../../../solutions/sem02/lesson05/task1.py). + +**Входные данные**: +- `costs` - двумерный массив чисел с плавающей точкой - количества ресурсов, требуемых для производства товаров (размерность $M \times N$); +- `resource_amounts` - одномерный массив чисел с плавающей точкой - доступное количество ресурсов (длина $M$); +- `demand_expected` - одномерный массив целых чисел - необходимое число товаров для удовлетворения ожидаемого спроса (длина $N$); + +**Выходные данные**: +- Булево значение: `True`, если компания сможет удовлетворить ожидаемый спрос, иначе - `False`. + +*Сторонние эффекты*: +- Если размеры входных массивов не согласованы, необходимо возбудить исключение `ShapeMismatchError`. + +## Задача 2. Без базиса не выйдет + +Дана квадратная матрица $A$. В строках матрицы $A$ записаны векторы N-мерного пространства. Также дан вектор $\vec{a}$. Ваша задача - реализовать функцию, которая бы выполняла следующие вычисления: +- проверяла, являются ли вектора матрицы $A$ базисом в N-мерном пространстве; +- вычисляла бы ортогональные проекции $\vec{a}$ на базисные вектора, а также вычисляла бы ортогональные составляющие каждой проекции, если матрица $A$ задает базис. + +Допишите код функции `get_projections_components` в файле [task2](../../../solutions/sem02/lesson05/task2.py). + +**Входные данные**: +- `matrix` - двумерный массив `np.ndarray` - описание потенциального базиса; +- `vector` - одномерный массив `np.ndarray` - вектор, проекции которого необходимо вычислить; + +**Выходные данные**: +- Кортеж (`tuple`) двумерных массивов. Первый элемент кортежа - ортогональные проекции вектора на базис, второй элемент - ортогональные составляющие проекций вектора на базис. В случае, если входная матрица не задает базис, элементы кортежа - `None`. + +*Сторонние эффекты*: +- Если матрица $A$ не является квадратной, необходимо возбудить исключение `ShapeMismatchError`; +- Если количество столбцов матрицы $A$ отлично от количества элементов входного вектора, необходимо возбудить исключение `ShapeMismatchError`. + +*Замечания*: +- Гарантируется, что `vector` - не нулевой вектор. + +## Задача 3. Адаптивная фильтрация + +Фильтрация нужна для того, чтобы отделить полезный сигнал от шума или помех. Адаптивная фильтрация — это более умный способ фильтрации, который используется, когда шум или сигнал меняются со временем. Адаптивный фильтр сам подстраивается под изменения, чтобы всегда эффективно убирать шум или выделять нужный сигнал. + +В данной задаче рассматривается формула адаптивной фильтрации, которая основана на использовании корреляционной матрицы и обучающей выборки для настройки фильтра. Формула имеет вид: + +$$ y = R^{-1} V_s $$ + +где: +- $y$ — выходной сигнал после фильтрации (матрица размером $M \times N$) +- $R^{-1}$ — обратная корреляционная матрица (матрица размером $M \times M$) +- $V_s$ — обрабатываемая выборка сигнала (матрица размером $M \times N$) + +Обратная корреляционная матрица $R^{-1}$ вычисляется следующим образом: + +$$ R^{-1} = (I + V_j A V_j^H)^{-1} = I - V_j (I + V_j^H V_j A)^{-1} V_j^H $$ + +где: +- $I$ — единичная матрица +- $V_j$ — обучающая выборка (матрица комплексных чисел размером $M \times K$) +- $A$ — диагональная матрица комплексных чисел (размером $K \times K$), которая определяет мощность подавления +- $V_j^H$ — матрица, которая получается поэлементным сопряжением матрицы $V_j$ и транспонированием результата (эрмитово сопряженная матрица) + +Напишите функцию, которая вычисляет выходной сигнал $y$ по формуле адаптивной фильтрации, используя предоставленные матрицы $V_s$, $V_j$ и $A$. + +Допишите код функции `adaptive_filter` в файле [task3](../../../solutions/sem02/lesson05/task3.py). + +**Входные данные**: +- `Vs` — матрица обрабатываемой выборки сигнала (тип: `numpy.ndarray`, размерность: $M \times N$) +- `Vj` — матрица обучающей выборки (тип: `numpy.ndarray`, размерность: $M \times K$) +- `diag_A` — диагональ матрицы A (тип: `numpy.ndarray`, размерность: $K$) + +**Выходные данные**: +- `y` — выходной сигнал после фильтрации (тип: `numpy.ndarray`, размерность: $M \times N$) + +*Сторонние эффекты*: +- Если размеры входных массивов не согласованы, необходимо возбудить исключение `ShapeMismatchError`. diff --git a/conditions/sem02/lesson07/images/task1_expected.png b/conditions/sem02/lesson07/images/task1_expected.png new file mode 100644 index 00000000..b7696dab Binary files /dev/null and b/conditions/sem02/lesson07/images/task1_expected.png differ diff --git a/conditions/sem02/lesson07/images/task2_expected.png b/conditions/sem02/lesson07/images/task2_expected.png new file mode 100644 index 00000000..81b13b82 Binary files /dev/null and b/conditions/sem02/lesson07/images/task2_expected.png differ diff --git a/conditions/sem02/lesson07/tasks.md b/conditions/sem02/lesson07/tasks.md new file mode 100644 index 00000000..319fdc38 --- /dev/null +++ b/conditions/sem02/lesson07/tasks.md @@ -0,0 +1,51 @@ +## Задача 1. Распределения на любой вкус + +Реализуйте функцию `visualize_diagrams`, которая позволяет построить диаграмму рассеяния данных и распределения данных вдоль координатных осей. + +Допишите код функции `visualize_diagrams` в файле [task1](../../../solutions/sem02/lesson07/task1.py). + +**Входные данные**: +- `abscissa` - одномерный `np.ndarray` чисел с плавающей точкой - абсциссы визуализируемых точек. +- `ordinates` - одномерный `np.ndarray` чисел с плавающей точкой - ординаты визуализируемых точек. +- `diagram_type` - строка, тип визуализации распределения данных вдоль осей. `diagram_type` должен принимать одно из трех значений: `hist` - в этом случае распределение строится в виде гистограммы, `violin` в этом случае распределение строится в виде скрипичной диаграммы, `box` - в этом случае распределение строится в виде ящика с усами. + +**Сторонние эффекты**: +- После выполнения функции на экране должно отображаться изображение с визуализацией, похожей на визуализацию из последнего примера с семинара (добавьте что-то от себя, например, поменяйте цвет или оформление): + +![expected-result](./images/task1_expected.png) +- Если размеры массивов `abscissa` и `ordinates` не равны, необходимо возбудить исключение `ShapeMismatchError`. +- Если значение `diagram_type` не является допустимым значением, необходимо возбудить `ValueError`. + +## Задача 2. Сердечная задача + +Представим, что вы работаете аналитиком данных в некоторой медицинской компании, которая занимается изготовлением кардио-имплантов. В данный момент кампания планирует запустить в серийное производство новый кардио-имплант, устанавливаемый при митральной недостаточности (неправильное функционирование митрального клапана сердца, при котором возникает обратное движение крови из левого желудочка в левое предсердие во время сокращения желудочков сердца вследствие неполного смыкания створок клапана). Прежде, чем запускать новую разработку в серийное производство было произведено исследования эффективности импланта. Исследование происходило следующим образом: +- У пациентов, принимающих участие в исследовании, фиксировалась текущая степень митральной недостаточности. Всего степеней митральное недостаточности 4: первая степень - самая легкая, четвертая - самая опасная. +- Затем участникам исследования устанавливался кардио-имплант. +- Спустя некоторое время повторно определялась степень митральной недостаточности. + +Данные о степенях митральной недостаточности пациентов до и после установки импланта были записаны в файл [`medic_data.json`](../../../solutions/sem02/lesson07/data/medic_data.json) в следующем формате: +```python +{ + "before": [ + "I", + "II", + ... + ], + "after": [ + "I", + "II", + ... + ] +} +``` + +Ключу `"before"` соответствует список со степенями митральной недостаточности пациентов до установки импланта, а ключу `"after"` - после. Сами степени записаны в виде строковых литералов, которые стоит интерпретировать, как числа, записанные латинскими цифрами. + +Ваша задача - реализовать функционал для визуализации распределения пациентов по степеням митральной недостаточности до и после установки импланта. Для этого вам необходимо прочитать данные из файла [`medic_data.json`](../../../solutions/sem02/lesson07/data/medic_data.json), рассчитать число пациентов для каждой группы митральной недостаточности, построить столбчатые диаграммы, сохранить изображение с диаграммами в память компьютера. + +В результате выполнения приведенной последовательности действий вы должны получить картинку, похожую на эту (добавьте что-то от себя, например, поменяйте цвет или оформление): +![expected-result](./images/task2_expected.png) + +Напишите код в файле [task2](../../../solutions/sem02/lesson07/task2.py). + +Проанализируйте, полученную вами диаграмму. Какой вывод об эффективности импланта можно сделать, если стадия I самая безопасная, а стадия IV - самая опасная? diff --git a/conditions/sem02/lesson08/gifs/labyrinth_example.gif b/conditions/sem02/lesson08/gifs/labyrinth_example.gif new file mode 100644 index 00000000..3af22fb9 Binary files /dev/null and b/conditions/sem02/lesson08/gifs/labyrinth_example.gif differ diff --git a/conditions/sem02/lesson08/gifs/modulated_signal_example.gif b/conditions/sem02/lesson08/gifs/modulated_signal_example.gif new file mode 100644 index 00000000..99496837 Binary files /dev/null and b/conditions/sem02/lesson08/gifs/modulated_signal_example.gif differ diff --git a/conditions/sem02/lesson08/gifs/waves.gif b/conditions/sem02/lesson08/gifs/waves.gif new file mode 100644 index 00000000..31f2764b Binary files /dev/null and b/conditions/sem02/lesson08/gifs/waves.gif differ diff --git a/conditions/sem02/lesson08/tasks.md b/conditions/sem02/lesson08/tasks.md new file mode 100644 index 00000000..43adaea1 --- /dev/null +++ b/conditions/sem02/lesson08/tasks.md @@ -0,0 +1,93 @@ +## Задача №1. Сигналы. + +В данной задаче вам предстоит реализовать анимацию модулированного сигнала. + +Модуляция — это процесс изменения параметров несущего сигнала (например, амплитуды, частоты или фазы) в зависимости от информационного (модулирующего) сигнала. В данной задаче рассматривается амплитудная модуляция (AM) , при которой амплитуда несущего сигнала изменяется в соответствии с модулирующим сигналом M(t). Математически модулированный сигнал описывается формулой: + +$$ s(t)=M(t)⋅sin(2πf_ct) $$ +где: +- $M(t)$ — функция модуляции (определяет изменение амплитуды несущего сигнала); +- $f_c$ — частота несущего сигнала. + +*Входные данные*: +- `modulation` - `Callable`-объект (функция) или `None`. Если передается функция, она определяет модуляцию сигнала. Если None, то модуляция не осуществляется, и используется только несущий сигнал. +- `fc` - Частота несущего сигнала (Гц). +- `num_frames` - Количество кадров анимации. +- `plot_duration` - Длительность интервала времени (в секундах), который будет отображаться на графике в каждый момент анимации. +- `time_step` - Шаг дискретизации времени ($Δt$), используемый для вычисления значений сигнала. По-умолчанию 0.001 секунд. +- `animation_step` - Шаг анимации, пределяющий, разницу по времени между кадрами, то есть насколько сдвигается график. По-умолчанию 0.01 секунд. +- `save_path` - Путь к файлу, куда будет сохранена анимация, если путь `""`, то сохранять не надо,. По-умолчанию `""`. + +*Выходные данные*: +- Анимация - График, показывающий динамику модулированного сигнала. На каждом кадре отображается фрагмент сигнала длительностью plot_duration. +- Файл GIF - Анимация сохраняется в файл по указанному пути save_path. + +**Требования**: + +В данной задаче у вас нет заготовки решения. Вы вольны решать задачу так, как считаете нужным. Однако при проверки ваших решений семинаристы будут уделять внимание следующим аспектам: + +- **Правильность решения**. Важно, чтобы ваше решение работало так, как ожидается. Решение должно включать считывание данных из файла, построение диаграммы на основе считанных данных, сохранение картинки в память компьютера. Неправильно работающие решения будут оценены в 0 баллов. +- **Структура решения**. Решение должно быть аккуратным. Код должен быть разбит на логические блоки: функции или методы класса. Решение, реализованное в императивном стиле (все команды выполнены на уровне модуля) будет оценено максимум в 5 баллов из 10. При разбиении кода на логические блоки избегайте смешения логики (вычисления и построение диаграмм в одной функции или чтение данных и построение диаграммы в одной функции). Смешение логики также будет штрафоваться на усмотрение семинариста. +- **Оформление**. Оформляйте ваш код аккуратно. Используйте `flake8` вместе с конфигом из корня репозитория для проверки качества вашего кода. Также избегайте повторений. Если один и тот же код был скопирован и использован два раза, семинарист может снизить вашу оценку на свое усмотрение. + +**Пример анимации**: + +![signal](./gifs/modulated_signal_example.gif) + +*Примечание*: Ваш вариант может отличаться от примера. + + + +# Задача №2 Выход есть! + +Волновой алгоритм (алгоритм Ли) — это метод поиска кратчайшего пути на двумерной сетке или графе. Он используется для нахождения пути между двумя точками в лабиринте, где каждая клетка может быть либо стеной (непроходимой), либо проходом (проходимой). Алгоритм основан на принципе "волнового распространения": из начальной точки (старта) волна распространяется во все стороны, постепенно заполняя доступные клетки, пока не достигнет конечной точки (финиша). + +**Основные шаги алгоритма**: +1. **Инициализация**: + - Начальная точка (старт) помечается числом `0`. + - Все остальные клетки лабиринта инициализируются значением, обозначающим "не посещено" (например, `-1`). + +2. **Распространение волны**: + - На каждом шаге алгоритм рассматривает все клетки, помеченные текущим значением (например, `n`). + - Для каждой такой клетки проверяются её соседи (вверх, вниз, влево, вправо). Если соседняя клетка является проходом (`1`) и ещё не посещена (значение `-1`), она помечается значением `n + 1`. + - Процесс продолжается до тех пор, пока волна не достигнет конечной точки (финиша) или не будут обработаны все доступные клетки. + +3. **Восстановление пути**: + - Если волна достигла финиша, кратчайший путь восстанавливается "обратным ходом": + - Начиная с финиша, двигаются к соседней клетке с меньшим значением (на единицу). + - Процесс повторяется, пока не будет достигнут старт. + - Если волна не достигла финиша (например, финиш окружён стенами), путь не существует. + +Необходимо реализовать функцию, которая будет анимировать процесс работы волнового алгоритма для поиска пути в лабиринте. Лабиринт представлен в виде двумерного массива `numpy.ndarray`, где: +- `0` — это стена (непроходимая клетка), +- `1` — это проход (проходимая клетка). + +Функция должна визуализировать процесс распространения волны и конечный путь, если он существует. Если путь не существует, функция должна сообщить об этом. + +*Входные аргументы*: + +- `maze` (`numpy.ndarray`) — двумерный массив, представляющий лабиринт. Элементы массива: + - `0` — стена, + - `1` — проход. +- `start` (`Tuple[int, int]`) — координаты начальной точки (строка, столбец). +- `end` (`Tuple[int, int]`) — координаты конечной точки (строка, столбец). +- `save_path` (`str`) — путь к файлу, в который нужно сохранить анимацию. Если строка пустая (`""`), то сохранять анимацию не нужно. + +*Выходное значение*: + +- Анимация - График, показывающий рабюоту волнового алгоритма. +- Файл GIF - Анимация сохраняется в файл по указанному пути save_path. + +**Требования**: + +В данной задаче у вас нет заготовки решения. Вы вольны решать задачу так, как считаете нужным. Однако при проверки ваших решений семинаристы будут уделять внимание следующим аспектам: + +- **Правильность решения**. Важно, чтобы ваше решение работало так, как ожидается. Решение должно включать считывание данных из файла, построение диаграммы на основе считанных данных, сохранение картинки в память компьютера. Неправильно работающие решения будут оценены в 0 баллов. +- **Структура решения**. Решение должно быть аккуратным. Код должен быть разбит на логические блоки: функции или методы класса. Решение, реализованное в императивном стиле (все команды выполнены на уровне модуля) будет оценено максимум в 5 баллов из 10. При разбиении кода на логические блоки избегайте смешения логики (вычисления и построение диаграмм в одной функции или чтение данных и построение диаграммы в одной функции). Смешение логики также будет штрафоваться на усмотрение семинариста. +- **Оформление**. Оформляйте ваш код аккуратно. Используйте `flake8` вместе с конфигом из корня репозитория для проверки качества вашего кода. Также избегайте повторений. Если один и тот же код был скопирован и использован два раза, семинарист может снизить вашу оценку на свое усмотрение. + +**Пример анимации**: + +![signal](./gifs/labyrinth_example.gif) + +*Примечание*: Ваш вариант может отличаться от примера. \ No newline at end of file diff --git a/homeworks/hw1/__init__.py b/deprecated_tests/__init__.py similarity index 100% rename from homeworks/hw1/__init__.py rename to deprecated_tests/__init__.py diff --git a/solutions/lesson02/__init__.py b/deprecated_tests/sem01/__init__.py similarity index 100% rename from solutions/lesson02/__init__.py rename to deprecated_tests/sem01/__init__.py diff --git a/solutions/lesson05/__init__.py b/deprecated_tests/sem01/tests/__init__.py similarity index 100% rename from solutions/lesson05/__init__.py rename to deprecated_tests/sem01/tests/__init__.py diff --git a/tests/test_lesson02_tasks.py b/deprecated_tests/sem01/tests/test_lesson02_tasks.py similarity index 93% rename from tests/test_lesson02_tasks.py rename to deprecated_tests/sem01/tests/test_lesson02_tasks.py index ae134994..f11403f4 100644 --- a/tests/test_lesson02_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson02_tasks.py @@ -1,12 +1,12 @@ import pytest -from solutions.lesson02.task1 import get_factorial -from solutions.lesson02.task2 import get_doubled_factorial -from solutions.lesson02.task3 import get_amount_of_ways_to_climb -from solutions.lesson02.task4 import get_multiplications_amount -from solutions.lesson02.task5 import get_gcd -from solutions.lesson02.task6 import get_sum_of_prime_divisors -from solutions.lesson02.task7 import is_palindrome +from solutions.sem01.lesson02.task1 import get_factorial +from solutions.sem01.lesson02.task2 import get_doubled_factorial +from solutions.sem01.lesson02.task3 import get_amount_of_ways_to_climb +from solutions.sem01.lesson02.task4 import get_multiplications_amount +from solutions.sem01.lesson02.task5 import get_gcd +from solutions.sem01.lesson02.task6 import get_sum_of_prime_divisors +from solutions.sem01.lesson02.task7 import is_palindrome @pytest.mark.parametrize( diff --git a/tests/test_lesson03_tasks.py b/deprecated_tests/sem01/tests/test_lesson03_tasks.py similarity index 92% rename from tests/test_lesson03_tasks.py rename to deprecated_tests/sem01/tests/test_lesson03_tasks.py index 157c1f8c..4b3fb50e 100644 --- a/tests/test_lesson03_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson03_tasks.py @@ -1,8 +1,8 @@ import pytest -from solutions.lesson03.task1 import flip_bits_in_range -from solutions.lesson03.task2 import get_cube_root -from solutions.lesson03.task3 import get_nth_digit +from solutions.sem01.lesson03.task1 import flip_bits_in_range +from solutions.sem01.lesson03.task2 import get_cube_root +from solutions.sem01.lesson03.task3 import get_nth_digit @pytest.mark.parametrize( diff --git a/tests/test_lesson04_tasks.py b/deprecated_tests/sem01/tests/test_lesson04_tasks.py similarity index 93% rename from tests/test_lesson04_tasks.py rename to deprecated_tests/sem01/tests/test_lesson04_tasks.py index c8278329..4110bdcc 100644 --- a/tests/test_lesson04_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson04_tasks.py @@ -1,12 +1,12 @@ import pytest import random -from solutions.lesson04.task1 import is_arithmetic_progression -from solutions.lesson04.task2 import merge_intervals -from solutions.lesson04.task3 import find_single_number -from solutions.lesson04.task4 import move_zeros_to_end -from solutions.lesson04.task5 import find_row_with_most_ones -from solutions.lesson04.task6 import count_cycles +from solutions.sem01.lesson04.task1 import is_arithmetic_progression +from solutions.sem01.lesson04.task2 import merge_intervals +from solutions.sem01.lesson04.task3 import find_single_number +from solutions.sem01.lesson04.task4 import move_zeros_to_end +from solutions.sem01.lesson04.task5 import find_row_with_most_ones +from solutions.sem01.lesson04.task6 import count_cycles @pytest.mark.parametrize("lst, expected", [ pytest.param([], True, id="empty_list"), diff --git a/tests/test_lesson05_tasks.py b/deprecated_tests/sem01/tests/test_lesson05_tasks.py similarity index 95% rename from tests/test_lesson05_tasks.py rename to deprecated_tests/sem01/tests/test_lesson05_tasks.py index 2ab63e3b..72ad6bc8 100644 --- a/tests/test_lesson05_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson05_tasks.py @@ -1,11 +1,11 @@ import pytest -from solutions.lesson05.task1 import is_palindrome -from solutions.lesson05.task2 import are_anagrams -from solutions.lesson05.task3 import is_punctuation -from solutions.lesson05.task4 import unzip -from solutions.lesson05.task5 import reg_validator -from solutions.lesson05.task6 import simplify_path +from solutions.sem01.lesson05.task1 import is_palindrome +from solutions.sem01.lesson05.task2 import are_anagrams +from solutions.sem01.lesson05.task3 import is_punctuation +from solutions.sem01.lesson05.task4 import unzip +from solutions.sem01.lesson05.task5 import reg_validator +from solutions.sem01.lesson05.task6 import simplify_path @pytest.mark.parametrize("s, expected", [ pytest.param("", True, id="empty_string"), diff --git a/tests/test_lesson06_tasks.py b/deprecated_tests/sem01/tests/test_lesson06_tasks.py similarity index 94% rename from tests/test_lesson06_tasks.py rename to deprecated_tests/sem01/tests/test_lesson06_tasks.py index 0abade4f..707d6609 100644 --- a/tests/test_lesson06_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson06_tasks.py @@ -1,9 +1,9 @@ import pytest -from solutions.lesson06.task1 import int_to_roman -from solutions.lesson06.task2 import get_len_of_longest_substring -from solutions.lesson06.task3 import is_there_any_good_subarray -from solutions.lesson06.task4 import count_unique_words +from solutions.sem01.lesson06.task1 import int_to_roman +from solutions.sem01.lesson06.task2 import get_len_of_longest_substring +from solutions.sem01.lesson06.task3 import is_there_any_good_subarray +from solutions.sem01.lesson06.task4 import count_unique_words @pytest.mark.parametrize("num, expected", [ diff --git a/tests/test_lesson08_tasks.py b/deprecated_tests/sem01/tests/test_lesson08_tasks.py similarity index 94% rename from tests/test_lesson08_tasks.py rename to deprecated_tests/sem01/tests/test_lesson08_tasks.py index 7d71e479..962ba4bd 100644 --- a/tests/test_lesson08_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson08_tasks.py @@ -2,8 +2,8 @@ import math import time -from solutions.lesson08.task1 import make_averager -from solutions.lesson08.task2 import collect_statistic +from solutions.sem01.lesson08.task1 import make_averager +from solutions.sem01.lesson08.task2 import collect_statistic def test_make_averager(): get_avg = make_averager(2) diff --git a/tests/test_lesson11_tasks.py b/deprecated_tests/sem01/tests/test_lesson11_tasks.py similarity index 99% rename from tests/test_lesson11_tasks.py rename to deprecated_tests/sem01/tests/test_lesson11_tasks.py index 88608f89..d0cd02ef 100644 --- a/tests/test_lesson11_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson11_tasks.py @@ -5,7 +5,7 @@ from io import StringIO -from solutions.lesson11.task1 import Vector2D +from solutions.sem01.lesson11.task1 import Vector2D def test_read_attributes(): diff --git a/tests/test_lesson12_tasks.py b/deprecated_tests/sem01/tests/test_lesson12_tasks.py similarity index 96% rename from tests/test_lesson12_tasks.py rename to deprecated_tests/sem01/tests/test_lesson12_tasks.py index 8635cff7..bab3e9bd 100644 --- a/tests/test_lesson12_tasks.py +++ b/deprecated_tests/sem01/tests/test_lesson12_tasks.py @@ -2,9 +2,9 @@ import pytest -from solutions.lesson12.task1 import chunked -from solutions.lesson12.task2 import circle -from solutions.lesson12.task3 import FileOut +from solutions.sem01.lesson12.task1 import chunked +from solutions.sem01.lesson12.task2 import circle +from solutions.sem01.lesson12.task3 import FileOut def finite_generator(): diff --git a/solutions/lesson08/__init__.py b/deprecated_tests/sem01/tests_hw/__init__.py similarity index 100% rename from solutions/lesson08/__init__.py rename to deprecated_tests/sem01/tests_hw/__init__.py diff --git a/solutions/lesson11/__init__.py b/deprecated_tests/sem01/tests_hw/hw1_test_data/__init__.py similarity index 100% rename from solutions/lesson11/__init__.py rename to deprecated_tests/sem01/tests_hw/hw1_test_data/__init__.py diff --git a/tests_hw/hw1_test_data/cache_test_data.py b/deprecated_tests/sem01/tests_hw/hw1_test_data/cache_test_data.py similarity index 100% rename from tests_hw/hw1_test_data/cache_test_data.py rename to deprecated_tests/sem01/tests_hw/hw1_test_data/cache_test_data.py diff --git a/tests_hw/test_hw1_tasks.py b/deprecated_tests/sem01/tests_hw/test_hw1_tasks.py similarity index 93% rename from tests_hw/test_hw1_tasks.py rename to deprecated_tests/sem01/tests_hw/test_hw1_tasks.py index ebc3771c..0ecf8a10 100644 --- a/tests_hw/test_hw1_tasks.py +++ b/deprecated_tests/sem01/tests_hw/test_hw1_tasks.py @@ -2,11 +2,11 @@ import uuid from unittest.mock import MagicMock, patch, Mock -from homeworks.hw1.aggregate_segmentation import aggregate_segmentation, ALLOWED_TYPES -from homeworks.hw1.backoff import backoff -from homeworks.hw1.cache import lru_cache -from homeworks.hw1.convert_exception import convert_exceptions_to_api_compitable_ones -from tests_hw.hw1_test_data.cache_test_data import ( +from homeworks.sem01.hw1.aggregate_segmentation import aggregate_segmentation, ALLOWED_TYPES +from homeworks.sem01.hw1.backoff import backoff +from homeworks.sem01.hw1.cache import lru_cache +from homeworks.sem01.hw1.convert_exception import convert_exceptions_to_api_compitable_ones +from .hw1_test_data.cache_test_data import ( TESTCASE_DATA, TESTCASE_IDS, ) diff --git a/deprecated_tests/sem02/tests/task3/test_lesson03_tasks.py b/deprecated_tests/sem02/tests/task3/test_lesson03_tasks.py new file mode 100644 index 00000000..40f1cc71 --- /dev/null +++ b/deprecated_tests/sem02/tests/task3/test_lesson03_tasks.py @@ -0,0 +1,496 @@ +import numpy as np +import pytest + +from solutions.sem02.lesson03.task1 import ShapeMismatchError as Task1ShapeMismatchError +from solutions.sem02.lesson03.task1 import ( + compute_poly_vectorized, + get_mutual_l2_distances_vectorized, + sum_arrays_vectorized, +) +from solutions.sem02.lesson03.task2 import ShapeMismatchError as Task2ShapeMismatchError +from solutions.sem02.lesson03.task2 import convert_from_sphere, convert_to_sphere +from solutions.sem02.lesson03.task3 import get_extremum_indices + + +class TestTask1: + @pytest.mark.parametrize( + "lhs, rhs, result", + [ + pytest.param([], [], [], id="empty"), + pytest.param([1], [2], [3], id="one-elem"), + pytest.param( + [10, 20, 30, 40, 50], [1, 2, 3, 4, 5], [11, 22, 33, 44, 55], id="five-elem" + ), + ], + ) + def test_sum_arrays(self, lhs: list, rhs: list, result: list): + assert np.all( + np.isclose(sum_arrays_vectorized(np.array(lhs), np.array(rhs)), np.array(result)) + ) + + @pytest.mark.parametrize( + "lhs, rhs", + [ + pytest.param([], [1], id="first-empty"), + pytest.param([1], [], id="second-empty"), + pytest.param([1, 2], [1], id="first-longer"), + pytest.param([1], [1, 2], id="second-longer"), + ], + ) + def test_sum_arrays_validate(self, lhs: list, rhs: list): + with pytest.raises(Task1ShapeMismatchError): + sum_arrays_vectorized(np.array(lhs), np.array(rhs)) + + @pytest.mark.parametrize( + "abscissa, result", + [ + pytest.param([], [], id="empty"), + pytest.param([1], [6], id="one-elem"), + pytest.param([1, 2, 3, 4, 5], [6, 17, 34, 57, 86], id="five-elem"), + ], + ) + def test_compute_poly(self, abscissa: list, result: list): + assert np.all(np.isclose(compute_poly_vectorized(np.array(abscissa)), np.array(result))) + + @staticmethod + def get_mutual_l2_distances_naive( + lhs: list[list[float]], + rhs: list[list[float]], + ) -> list[list[float]]: + if len(lhs[0]) != len(rhs[0]): + raise Task1ShapeMismatchError + + return [ + [ + sum((lhs[i][k] - rhs[j][k]) ** 2 for k in range(len(lhs[0]))) ** 0.5 + for j in range(len(rhs)) + ] + for i in range(len(lhs)) + ] + + @pytest.mark.parametrize( + "lhs, rhs", + [ + pytest.param([[]], [[]], id="empty"), + pytest.param([[1, 2, 3]], [[10, 20, 30]], id="one-vector"), + pytest.param( + [ + [1, 2, 3, 4], + [5, 6, 7, 8], + [9, 10, 11, 12], + [13, 14, 15, 16], + [17, 18, 19, 20], + ], + [ + [21, 22, 23, 24], + [25, 26, 27, 28], + [29, 30, 31, 32], + [33, 34, 35, 36], + [37, 38, 39, 40], + ], + id="five-vectors", + ), + ], + ) + def test_get_mutual_l2_distances(self, lhs: list, rhs: list): + assert np.all( + np.isclose( + get_mutual_l2_distances_vectorized(np.array(lhs), np.array(rhs)), + self.get_mutual_l2_distances_naive(lhs, rhs), + ) + ) + + @pytest.mark.parametrize( + "lhs, rhs", + [ + pytest.param([[]], [[1]], id="first-empty"), + pytest.param([[1]], [[]], id="second-empty"), + pytest.param([[1, 2], [3, 4]], [[1], [2]], id="first-longer"), + pytest.param([[1], [2]], [[1, 2], [3, 4]], id="second-longer"), + ], + ) + def test_get_mutual_l2_distances_validate(self, lhs: list, rhs: list): + with pytest.raises(Task1ShapeMismatchError): + sum_arrays_vectorized(np.array(lhs), np.array(rhs)) + + +class TestTask2: + @pytest.mark.parametrize( + "distances, azimuth, inclination, expected_x, expected_y, expected_z", + [ + pytest.param( + np.array([1.0]), + np.array([0.0]), + np.array([np.pi / 2]), + np.array([1.0]), + np.array([0.0]), + np.array([0.0]), + id="on_positive_x_axis", + ), + pytest.param( + np.array([2.0]), + np.array([np.pi / 2]), + np.array([np.pi / 2]), + np.array([0.0]), + np.array([2.0]), + np.array([0.0]), + id="on_positive_y_axis", + ), + pytest.param( + np.array([3.0]), + np.array([0.0]), + np.array([0.0]), + np.array([0.0]), + np.array([0.0]), + np.array([3.0]), + id="on_positive_z_axis", + ), + pytest.param( + np.array([5.0]), + np.array([np.pi]), + np.array([np.pi / 2]), + np.array([-5.0]), + np.array([0.0]), + np.array([0.0]), + id="on_negative_x_axis", + ), + ], + ) + def test_convert_from_sphere( + self, distances, azimuth, inclination, expected_x, expected_y, expected_z + ): + x, y, z = convert_from_sphere(distances, azimuth, inclination) + assert np.allclose(x, expected_x) + assert np.allclose(y, expected_y) + assert np.allclose(z, expected_z) + + @pytest.mark.parametrize( + "expected_distances, expected_azimuth, expected_inclination, x, y, z", + [ + pytest.param( + np.array([1.0]), + np.array([0.0]), + np.array([np.pi / 2]), + np.array([1.0]), + np.array([0.0]), + np.array([0.0]), + id="on-positive-x-axis", + ), + pytest.param( + np.array([2.0]), + np.array([np.pi / 2]), + np.array([np.pi / 2]), + np.array([0.0]), + np.array([2.0]), + np.array([0.0]), + id="on-positive-y-axis", + ), + pytest.param( + np.array([3.0]), + np.array([0.0]), + np.array([0.0]), + np.array([0.0]), + np.array([0.0]), + np.array([3.0]), + id="on-positive-z-axis", + ), + pytest.param( + np.array([5.0]), + np.array([np.pi]), + np.array([np.pi / 2]), + np.array([-5.0]), + np.array([0.0]), + np.array([0.0]), + id="on-negative-x-axis", + ), + ], + ) + def test_convert_to_sphere( + self, expected_distances, expected_azimuth, expected_inclination, x, y, z + ): + distances, azimuth, inclination = convert_to_sphere(x, y, z) + assert np.allclose(distances, expected_distances) + assert np.allclose(azimuth, expected_azimuth) + assert np.allclose(inclination, expected_inclination) + + @pytest.mark.parametrize( + "dist, az, inc", + [ + pytest.param(np.array([1, 2]), np.array([0]), np.array([0]), id="dist-longer"), + pytest.param(np.array([1]), np.array([0, 1]), np.array([0]), id="azimuth-longer"), + pytest.param(np.array([1]), np.array([0]), np.array([0, 1]), id="inclination-longer"), + pytest.param( + np.array([[1, 2], [2, 3], [2, 3]]), + np.array([[1, 2, 3], [2, 3, 4]]), + np.array([[1, 2, 3], [2, 3, 4]]), + id="dist-shape-mis-2d", + ), + pytest.param( + np.array([[1, 2, 3], [2, 3, 4]]), + np.array([[1, 2], [2, 3], [2, 3]]), + np.array([[1, 2, 3], [2, 3, 4]]), + id="azimuth-shape-mis-2d", + ), + pytest.param( + np.array([[1, 2, 3], [2, 3, 4]]), + np.array([[1, 2, 3], [2, 3, 4]]), + np.array([[1, 2], [2, 3], [2, 3]]), + id="inclination-shape-mis-2d", + ), + ], + ) + def test_shape_mismatch_from_sphere(self, dist, az, inc): + with pytest.raises(Task2ShapeMismatchError): + convert_from_sphere(dist, az, inc) + + @pytest.mark.parametrize( + "x, y, z", + [ + pytest.param(np.array([1, 2]), np.array([0]), np.array([0]), id="x-longer"), + pytest.param(np.array([1]), np.array([0, 1]), np.array([0]), id="y-longer"), + pytest.param(np.array([1]), np.array([0]), np.array([0, 1]), id="z-longer"), + pytest.param( + np.array([[1, 2], [2, 3], [2, 3]]), + np.array([[1, 2, 3], [2, 3, 4]]), + np.array([[1, 2, 3], [2, 3, 4]]), + id="x-shape-mis-2d", + ), + pytest.param( + np.array([[1, 2, 3], [2, 3, 4]]), + np.array([[1, 2], [2, 3], [2, 3]]), + np.array([[1, 2, 3], [2, 3, 4]]), + id="y-shape-mis-2d", + ), + pytest.param( + np.array([[1, 2, 3], [2, 3, 4]]), + np.array([[1, 2, 3], [2, 3, 4]]), + np.array([[1, 2], [2, 3], [2, 3]]), + id="z-shape-mis-2d", + ), + ], + ) + def test_shape_mismatch_to_sphere(self, x, y, z): + with pytest.raises(Task2ShapeMismatchError): + convert_to_sphere(x, y, z) + + def test_convert_from_sphere_2d(self): + distances = np.array( + [ + [1.0, 2.0, 3.0, 4.0, 5.0], + [2.5, 3.5, 1.5, 4.5, 0.5], + [1.0, 1.0, 2.0, 2.0, 3.0], + [3.0, 2.0, 1.0, 2.0, 1.0], + [5.0, 4.0, 3.0, 2.0, 1.0], + ] + ) + + azimuth = np.array( + [ + [0.0, np.pi / 2, np.pi, -np.pi / 2, np.pi / 4], + [np.pi / 3, -np.pi / 3, 0.0, np.pi / 6, -np.pi / 6], + [np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2], + [0.0, 0.0, 0.0, 0.0, 0.0], + [-np.pi / 4, np.pi / 3, -np.pi / 3, np.pi / 6, -np.pi / 6], + ] + ) + + inclination = np.array( + [ + [np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2, np.pi / 2], + [np.pi / 3, np.pi / 4, np.pi / 6, np.pi / 3, np.pi / 4], + [0.0, np.pi / 2, np.pi, np.pi / 3, 2 * np.pi / 3], + [np.pi / 4, np.pi / 3, np.pi / 2, 2 * np.pi / 3, np.pi], + [np.pi / 6, np.pi / 3, np.pi / 2, 2 * np.pi / 3, 5 * np.pi / 6], + ] + ) + + expected_x = np.array( + [ + [1.00000000e00, 1.22464680e-16, -3.00000000e00, 2.44929360e-16, 3.53553391e00], + [1.08253175e00, 1.23743687e00, 7.50000000e-01, 3.37500000e00, 3.06186218e-01], + [0.00000000e00, 6.12323400e-17, 1.49975978e-32, 1.06057524e-16, 1.59086286e-16], + [2.12132034e00, 1.73205081e00, 1.00000000e00, 1.73205081e00, 1.22464680e-16], + [1.76776695e00, 1.73205081e00, 1.50000000e00, 1.50000000e00, 4.33012702e-01], + ] + ) + + expected_y = np.array( + [ + [0.00000000e00, 2.00000000e00, 3.67394040e-16, -4.00000000e00, 3.53553391e00], + [1.87500000e00, -2.14330352e00, 0.00000000e00, 1.94855716e00, -1.76776695e-01], + [0.00000000e00, 1.00000000e00, 2.44929360e-16, 1.73205081e00, 2.59807621e00], + [0.00000000e00, 0.00000000e00, 0.00000000e00, 0.00000000e00, 0.00000000e00], + [-1.76776695e00, 3.00000000e00, -2.59807621e00, 8.66025404e-01, -2.50000000e-01], + ] + ) + + expected_z = np.array( + [ + [6.12323400e-17, 1.22464680e-16, 1.83697020e-16, 2.44929360e-16, 3.06161700e-16], + [1.25000000e00, 2.47487373e00, 1.29903811e00, 2.25000000e00, 3.53553391e-01], + [1.00000000e00, 6.12323400e-17, -2.00000000e00, 1.00000000e00, -1.50000000e00], + [2.12132034e00, 1.00000000e00, 6.12323400e-17, -1.00000000e00, -1.00000000e00], + [4.33012702e00, 2.00000000e00, 1.83697020e-16, -1.00000000e00, -8.66025404e-01], + ] + ) + + x, y, z = convert_from_sphere(distances, azimuth, inclination) + + assert x.shape == (5, 5) + assert y.shape == (5, 5) + assert z.shape == (5, 5) + + assert np.allclose(x, expected_x, atol=1e-6) + assert np.allclose(y, expected_y, atol=1e-6) + assert np.allclose(z, expected_z, atol=1e-6) + + def test_convert_to_sphere_2d(self): + x = np.array( + [ + [1.0, 0.0, -1.0, 0.0, 1.0], + [0.0, 2.0, 0.0, -2.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0], + [3.0, -3.0, 3.0, -3.0, 0.0], + [1.0, -1.0, 1.0, -1.0, 2.0], + ] + ) + + y = np.array( + [ + [0.0, 1.0, 0.0, -1.0, 1.0], + [2.0, 0.0, -2.0, 0.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 4.0], + [1.0, 1.0, -1.0, -1.0, 0.0], + ] + ) + + z = np.array( + [ + [0.0, 0.0, 0.0, 0.0, np.sqrt(2)], + [0.0, 0.0, 0.0, 0.0, np.sqrt(2)], + [0.0, 1.0, -1.0, 2.0, -2.0], + [0.0, 0.0, 3.0, -3.0, 0.0], + [0.0, 0.0, 0.0, 0.0, 0.0], + ] + ) + + expected_distances = np.array( + [ + [1.0, 1.0, 1.0, 1.0, 2.0], + [2.0, 2.0, 2.0, 2.0, 2.0], + [0.0, 1.0, 1.0, 2.0, 2.0], + [3.0, 3.0, 4.24264069, 4.24264069, 4.0], + [1.41421356, 1.41421356, 1.41421356, 1.41421356, 2.0], + ] + ) + + expected_azimuth = np.array( + [ + [0.0, 1.57079633, 3.14159265, -1.57079633, 0.78539816], + [1.57079633, 0.0, -1.57079633, 3.14159265, 0.78539816], + [0.0, 0.0, 0.0, 0.0, 0.0], + [0.0, 3.14159265, 0.0, 3.14159265, 1.57079633], + [0.78539816, 2.35619449, -0.78539816, -2.35619449, 0.0], + ] + ) + + expected_inclination = np.array( + [ + [1.57079633, 1.57079633, 1.57079633, 1.57079633, 0.78539816], + [1.57079633, 1.57079633, 1.57079633, 1.57079633, 0.78539816], + [0.0, 0.0, 3.14159265, 0.0, 3.14159265], + [1.57079633, 1.57079633, 0.78539816, 2.35619449, 1.57079633], + [1.57079633, 1.57079633, 1.57079633, 1.57079633, 1.57079633], + ] + ) + + distances, azimuth, inclination = convert_to_sphere(x, y, z) + + assert distances.shape == (5, 5) + assert azimuth.shape == (5, 5) + assert inclination.shape == (5, 5) + + assert np.allclose(distances, expected_distances, atol=1e-6) + assert np.allclose(azimuth, expected_azimuth, atol=1e-6) + assert np.allclose(inclination, expected_inclination, atol=1e-6) + + +class TestTask3: + @pytest.mark.parametrize( + "ordinates, expected_mins, expected_maxs", + [ + pytest.param( + np.array([1.0, 3.0, 2.0]), + np.array([], dtype=int), + np.array([1]), + id="single_maximum", + ), + pytest.param( + np.array([3.0, 1.0, 2.0]), + np.array([1]), + np.array([], dtype=int), + id="single_minimum", + ), + pytest.param( + np.array([1.0, 3.0, 1.0, 4.0, 2.0]), + np.array([2]), + np.array([1, 3]), + id="min_and_max", + ), + pytest.param( + np.array([1.0, 2.0, 2.0, 1.0]), + np.array([], dtype=int), + np.array([], dtype=int), + id="plateau_no_extremum", + ), + pytest.param( + np.array([1.0, 0.0, 0.0, 1.0]), + np.array([], dtype=int), + np.array([], dtype=int), + id="plateau_no_extremum2", + ), + pytest.param( + np.array([1.0, 2.0, 3.0, 4.0]), + np.array([], dtype=int), + np.array([], dtype=int), + id="strictly_increasing", + ), + pytest.param( + np.array([4.0, 3.0, 2.0, 1.0]), + np.array([], dtype=int), + np.array([], dtype=int), + id="strictly_decreasing", + ), + pytest.param( + np.array([0.0, 1.0, 0.0, 2.0, 0.0, 3.0, 0.0]), + np.array([2, 4]), + np.array([1, 3, 5]), + id="alternating_extrema", + ), + pytest.param( + np.array([5.0, 5.0, 5.0, 5.0]), + np.array([], dtype=int), + np.array([], dtype=int), + id="constant_array", + ), + pytest.param( + np.array([5.0, 3.0, 3.0, 3.0, 0.0]), + np.array([], dtype=int), + np.array([], dtype=int), + id="edges_ignored", + ), + ], + ) + def test_get_extremum_indices(self, ordinates, expected_mins, expected_maxs): + mins, maxs = get_extremum_indices(ordinates) + + assert np.array_equal(mins, expected_mins), f"Expected mins {expected_mins}, got {mins}" + assert np.array_equal(maxs, expected_maxs), f"Expected maxs {expected_maxs}, got {maxs}" + + def test_get_extremum_indices_validation(self): + with pytest.raises(ValueError): + get_extremum_indices(np.array([1.0])) + + with pytest.raises(ValueError): + get_extremum_indices(np.array([1.0, 2.0])) diff --git a/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py b/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py new file mode 100644 index 00000000..37d249b7 --- /dev/null +++ b/deprecated_tests/sem02/tests/task4/test_lesson04_tasks.py @@ -0,0 +1,356 @@ +import os + +import numpy as np +import pytest + +from solutions.sem02.lesson04.task1 import blur_image, pad_image +from solutions.sem02.lesson04.task2 import get_dominant_color_info + +DATA_PATH = os.path.join("test_data", "lesson04") + + +class TestTask1: + @pytest.mark.parametrize( + "image, pad_size, expected", + [ + pytest.param( + np.array([[5]], dtype=np.float32), + 1, + np.array( + [ + [0, 0, 0], + [0, 5, 0], + [0, 0, 0], + ], + dtype=np.float32, + ), + id="2d_single_pixel_pad_1", + ), + pytest.param( + np.array([[5]], dtype=np.float32), + 2, + np.array( + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 5, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ], + dtype=np.float32, + ), + id="2d_single_pixel_pad_2", + ), + pytest.param( + np.array([[1, 2], [3, 4]], dtype=np.uint8), + 1, + np.array( + [[0, 0, 0, 0], [0, 1, 2, 0], [0, 3, 4, 0], [0, 0, 0, 0]], + dtype=np.uint8, + ), + id="2d_pad_1", + ), + pytest.param( + np.array([[1, 2], [3, 4]], dtype=np.uint8), + 2, + np.array( + [ + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 1, 2, 0, 0], + [0, 0, 3, 4, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], + dtype=np.uint8, + ), + id="2d_pad_2", + ), + pytest.param( + np.array([[1, 2]], dtype=np.uint8), + 2, + np.array( + [ + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 1, 2, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], + dtype=np.uint8, + ), + id="2d_row_pad_2", + ), + pytest.param( + np.array([[1], [2]], dtype=np.uint8), + 2, + np.array( + [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 2, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ], + dtype=np.uint8, + ), + id="2d_col_pad_2", + ), + pytest.param( + np.array([[[10, 20, 30], [40, 50, 60]]], dtype=np.uint8), + 1, + np.array( + [ + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [10, 20, 30], [40, 50, 60], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], + ], + dtype=np.uint8, + ), + id="3d_rgb_pad_1", + ), + pytest.param( + np.array([[[10, 20, 30], [40, 50, 60]]], dtype=np.uint8), + 2, + np.array( + [ + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + [ + [0, 0, 0], + [0, 0, 0], + [10, 20, 30], + [40, 50, 60], + [0, 0, 0], + [0, 0, 0], + ], + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ], + ], + dtype=np.uint8, + ), + id="3d_rgb_pad_2", + ), + pytest.param( + np.zeros((2, 3), dtype=int), + 1, + np.zeros((4, 5), dtype=int), + id="2d_zeros_pad_1", + ), + pytest.param( + np.arange(4095 * 4095).reshape(4095, 4095) % 256, + 100, + np.load(os.path.join(DATA_PATH, "test_task11_data_res.npy")), + id="large_data", + ), + ], + ) + def test_pad_image(self, image, pad_size, expected): + result = pad_image(image.astype(np.uint8), pad_size) + + assert np.array_equal(result, expected) + assert result.dtype == np.uint8 + + def test_pad_size_validate(self): + image = np.array([[1, 2], [3, 4]]) + + with pytest.raises(ValueError): + pad_image(image, 0) + + with pytest.raises(ValueError): + pad_image(image, -1) + + @pytest.mark.parametrize( + "image, kernel_size, expected", + [ + pytest.param( + np.array([[10, 20], [30, 40]], dtype=np.uint8), + 1, + np.array([[10, 20], [30, 40]], dtype=np.uint8), + id="kernel_size_1", + ), + pytest.param( + np.array([[100, 100], [100, 100]], dtype=np.uint8), + 3, + np.array([[44, 44], [44, 44]], dtype=np.uint8), + id="2d_constant_blur_3", + ), + pytest.param( + np.array([[0, 0, 0], [0, 255, 0], [0, 0, 0]], dtype=np.uint8), + 3, + np.array([[28, 28, 28], [28, 28, 28], [28, 28, 28]], dtype=np.uint8), + id="2d_single_pixel_blur_3", + ), + pytest.param( + np.array([[100]], dtype=np.uint8), + 3, + np.array([[11]], dtype=np.uint8), + id="2d_1x1_blur_3", + ), + pytest.param( + np.array( + [[[100, 68, 50], [179, 30, 245]], [[40, 90, 235], [38, 70, 210]]], + dtype=np.uint8, + ), + 3, + np.array( + [[[39, 28, 82], [39, 28, 82]], [[39, 28, 82], [39, 28, 82]]], + dtype=np.uint8, + ), + id="3d_blur_3", + ), + pytest.param( + np.arange(4095 * 4095 * 2).reshape(4095, 4095 * 2) % 256, + 5, + np.load(os.path.join(DATA_PATH, "test_task12_data_res.npy")), + id="large_data", + ), + ], + ) + def test_blur_image(self, image, kernel_size, expected): + result = blur_image(image.astype(np.uint8), kernel_size) + + assert result.shape == expected.shape + assert result.dtype == expected.dtype + assert np.all(np.abs(result.astype(np.int16) - expected.astype(np.int16)) < 2) + + def test_blur_image_validate(self): + image = np.array([[1, 2], [3, 4]], dtype=np.uint8) + + with pytest.raises(ValueError): + blur_image(image, 2) + with pytest.raises(ValueError): + blur_image(image, 4) + + with pytest.raises(ValueError): + blur_image(image, 0) + with pytest.raises(ValueError): + blur_image(image, -1) + + +class TestTask2: + @pytest.mark.parametrize( + "image, threshold, expected_color, expected_ratio", + [ + pytest.param( + np.array([[100, 100], [100, 100]], dtype=np.uint8), + 5, + [100], + 1.0, + id="uniform_image", + ), + pytest.param( + np.array([[50, 52], [51, 100]], dtype=np.uint8), + 3, + [50, 51, 52], + 0.75, + id="close_colors_merged", + ), + pytest.param( + np.array([[0, 0], [10, 11]], dtype=np.uint8), + 10, + [0], + 0.5, + id="distant_colors_not_merged", + ), + pytest.param( + np.array([[21, 20, 10], [20, 30, 20]], dtype=np.uint8), + 1, + [20], + 0.5, + id="threshold_1_only_exact", + ), + pytest.param( + np.array([[0, 100, 200]], dtype=np.uint8), + 10, + [0, 100, 200], + 1 / 3, + id="all_colors_different", + ), + pytest.param( + np.array([[100, 100, 100, 102, 104, 106, 108]], dtype=np.uint8), + 3, + [102], + 5 / 7, + id="chain_within_threshold", + ), + pytest.param( + np.array([[255, 0, 0]], dtype=np.uint8), + 20, + [0], + 2 / 3, + id="marginal_values_1", + ), + pytest.param( + np.array([[0, 255, 0]], dtype=np.uint8), + 20, + [0], + 2 / 3, + id="marginal_values_2", + ), + pytest.param( + np.array([[0, 0, 255]], dtype=np.uint8), + 20, + [0], + 2 / 3, + id="marginal_values_3", + ), + pytest.param( + np.load(os.path.join(DATA_PATH, "test_task2_data1.npy")), + 10, + list(range(100 - 11, 100 + 11)), + 0.166868359375, + id="large_data", + ), + ], + ) + def test_get_dominant_color_info( + self, image, threshold, expected_color, expected_ratio + ): + color, ratio_percent = get_dominant_color_info( + image.astype(np.uint8), threshold + ) + + assert color in expected_color + assert (abs(ratio_percent - expected_ratio * 100) < 1e-6) or ( + abs(ratio_percent - expected_ratio) < 1e-6 + ) + assert isinstance(color, np.uint8) + + def test_get_dominant_color_info_validate(self): + image = np.array([[0, 255]], dtype=np.uint8) + + with pytest.raises(ValueError, match="threshold must be positive"): + get_dominant_color_info(image, 0) + + with pytest.raises(ValueError, match="threshold must be positive"): + get_dominant_color_info(image, -1) diff --git a/deprecated_tests/sem02/tests/task5/test_lesson05_tasks.py b/deprecated_tests/sem02/tests/task5/test_lesson05_tasks.py new file mode 100644 index 00000000..aeb37ebc --- /dev/null +++ b/deprecated_tests/sem02/tests/task5/test_lesson05_tasks.py @@ -0,0 +1,226 @@ +import os + +import numpy as np +import pytest + +from solutions.sem02.lesson05.task1 import ShapeMismatchError as Task1ShapeMismatchError +from solutions.sem02.lesson05.task1 import can_satisfy_demand +from solutions.sem02.lesson05.task2 import ShapeMismatchError as Task2ShapeMismatchError +from solutions.sem02.lesson05.task2 import get_projections_components +from solutions.sem02.lesson05.task3 import ShapeMismatchError as Task3ShapeMismatchError +from solutions.sem02.lesson05.task3 import adaptive_filter + +DATA_PATH = os.path.join("tests", "test_data", "lesson05") + + +class TestTask1: + @pytest.mark.parametrize( + "costs, resource_amounts, demand_expected, expected", + [ + pytest.param( + np.eye(2), + np.array([3.0, 3.0]), + np.array([2, 2]), + True, + id="identity_enough", + ), + pytest.param( + np.eye(2), + np.array([2.0, 2.0]), + np.array([3, 3]), + False, + id="identity_not_enough", + ), + pytest.param( + np.array([[1.0, 2.0], [3.0, 4.0]]), + np.array([10.0, 20.0]), + np.array([2, 3]), + True, + id="general_enough", + ), + pytest.param( + np.array([[1.0, 2.0], [3.0, 4.0]]), + np.array([7.0, 17.0]), + np.array([2, 3]), + False, + id="general_not_enough", + ), + pytest.param( + np.array([[1.0]]), + np.array([5.0]), + np.array([5]), + True, + id="single_resource_product_exact", + ), + pytest.param( + np.array([[1.0, 0.0, 1.0], [0.0, 1.0, 1.0]]), + np.array([3.0, 3.0]), + np.array([1, 1, 1]), + True, + id="non_square_costs_enough", + ), + pytest.param( + np.array([[1.0, 0.0, 1.0], [0.0, 1.0, 1.0]]), + np.array([1.0, 1.0]), + np.array([1, 1, 1]), + False, + id="non_square_costs_not_enough", + ), + pytest.param( + np.eye(3), + np.array([0.0, 0.0, 0.0]), + np.array([0, 0, 0]), + True, + id="zero_demand", + ), + pytest.param( + np.eye(2), + np.full(shape=2, fill_value=3), + np.full(shape=2, fill_value=2), + True, + id="notebook_satisfy", + ), + pytest.param( + np.eye(2), + np.full(shape=2, fill_value=2), + np.full(shape=2, fill_value=3), + False, + id="notebook_not_satisfy", + ), + ], + ) + def test_can_satisfy_demand( + self, costs, resource_amounts, demand_expected, expected + ): + assert can_satisfy_demand(costs, resource_amounts, demand_expected) == expected + + def test_can_satisfy_demand_validate(self): + with pytest.raises(Task1ShapeMismatchError): + can_satisfy_demand( + np.array([[1.0, 2.0, 3.0]]), + np.array([1.0, 2.0]), + np.array([1]), + ) + + with pytest.raises(Task1ShapeMismatchError): + can_satisfy_demand( + np.array([[1.0, 2.0]]), + np.array([1.0]), + np.array([1, 2, 3]), + ) + + +class TestTask2: + @pytest.mark.parametrize( + "matrix, vector, proj_expected, orth_expected", + [ + pytest.param( + np.diag([2.0, 3.0]), + np.array([1.0, 2.0]), + np.array([[1.0, 0.0], [0.0, 2.0]]), + np.array([[0.0, 2.0], [1.0, 0.0]]), + id="diagonal_basis", + ), + pytest.param( + np.array([[1.0, 0.0], [1.0, 1.0]]), + np.array([0.0, 1.0]), + np.array([[0.0, 0.0], [0.5, 0.5]]), + np.array([[0.0, 1.0], [-0.5, 0.5]]), + id="non_orthogonal_basis", + ), + pytest.param( + np.eye(3), + np.array([1.0, 2.0, 3.0]), + np.array([[1.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 3.0]]), + np.array([[0.0, 2.0, 3.0], [1.0, 0.0, 3.0], [1.0, 2.0, 0.0]]), + id="identity_3d", + ), + pytest.param( + np.array([[1.0, 2.0], [2.0, 4.0]]), + np.array([0.0, 1.0]), + None, + None, + id="singular_not_basis", + ), + pytest.param( + np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]), + np.array([1.0, 2.0, 3.0]), + None, + None, + id="rank_deficient_3d", + ), + pytest.param( + np.diag([2, 3]), + np.arange(start=1, stop=3), + np.array([[1, 0], [0, 2]]), + np.array([[0, 2], [1, 0]]), + id="notebook_diagonal", + ), + pytest.param( + np.array([[1, 0], [1, 1]]), + np.array([0, 1]), + np.array([[0, 0], [0.5, 0.5]]), + np.array([[0, 1], [-0.5, 0.5]]), + id="notebook_non_orthogonal", + ), + pytest.param( + np.array([[1, 2], [2, 4]]), + np.array([0, 1]), + None, + None, + id="notebook_singular", + ), + ], + ) + def test_get_projections_components( + self, matrix, vector, proj_expected, orth_expected + ): + projections, orthogonals = get_projections_components(matrix, vector) + + if proj_expected is None: + assert projections is None + assert orthogonals is None + else: + assert np.allclose(projections, proj_expected) + assert np.allclose(orthogonals, orth_expected) + + def test_get_projections_components_validate(self): + with pytest.raises(Task2ShapeMismatchError): + get_projections_components( + np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), + np.array([1.0, 2.0, 3.0]), + ) + + with pytest.raises(Task2ShapeMismatchError): + get_projections_components( + np.array([[1.0, 0.0], [0.0, 1.0]]), + np.array([1.0, 2.0, 3.0]), + ) + + +class TestTask3: + def test_adaptive_filter(self): + diag_A = np.load(os.path.join(DATA_PATH, "diag_A_data.npy")) + Vj = np.load(os.path.join(DATA_PATH, "Vj_data.npy")) + Vs = np.load(os.path.join(DATA_PATH, "Vs_data.npy")) + y_expected = np.load(os.path.join(DATA_PATH, "y_data.npy")) + + y = adaptive_filter(Vs, Vj, diag_A) + + assert y.shape == y_expected.shape + assert np.allclose(y, y_expected) + + def test_adaptive_filter_validate(self): + with pytest.raises(Task3ShapeMismatchError): + adaptive_filter( + np.array([[1.0], [2.0]]), + np.array([[1.0], [2.0], [3.0]]), + np.array([1.0]), + ) + + with pytest.raises(Task3ShapeMismatchError): + adaptive_filter( + np.array([[1.0], [2.0]]), + np.array([[1.0, 2.0], [3.0, 4.0]]), + np.array([1.0, 2.0, 3.0]), + ) diff --git a/deprecated_tests/sem02/tests/test_data/lesson04/test_task11_data_res.npy b/deprecated_tests/sem02/tests/test_data/lesson04/test_task11_data_res.npy new file mode 100644 index 00000000..3e88be88 Binary files /dev/null and b/deprecated_tests/sem02/tests/test_data/lesson04/test_task11_data_res.npy differ diff --git a/deprecated_tests/sem02/tests/test_data/lesson04/test_task12_data_res.npy b/deprecated_tests/sem02/tests/test_data/lesson04/test_task12_data_res.npy new file mode 100644 index 00000000..394c3079 Binary files /dev/null and b/deprecated_tests/sem02/tests/test_data/lesson04/test_task12_data_res.npy differ diff --git a/deprecated_tests/sem02/tests/test_data/lesson04/test_task2_data1.npy b/deprecated_tests/sem02/tests/test_data/lesson04/test_task2_data1.npy new file mode 100644 index 00000000..8adda8ab Binary files /dev/null and b/deprecated_tests/sem02/tests/test_data/lesson04/test_task2_data1.npy differ diff --git a/deprecated_tests/sem02/tests/test_data/lesson05/Vj_data.npy b/deprecated_tests/sem02/tests/test_data/lesson05/Vj_data.npy new file mode 100644 index 00000000..8b9b5f7a Binary files /dev/null and b/deprecated_tests/sem02/tests/test_data/lesson05/Vj_data.npy differ diff --git a/deprecated_tests/sem02/tests/test_data/lesson05/Vs_data.npy b/deprecated_tests/sem02/tests/test_data/lesson05/Vs_data.npy new file mode 100644 index 00000000..da77bd6d Binary files /dev/null and b/deprecated_tests/sem02/tests/test_data/lesson05/Vs_data.npy differ diff --git a/deprecated_tests/sem02/tests/test_data/lesson05/diag_A_data.npy b/deprecated_tests/sem02/tests/test_data/lesson05/diag_A_data.npy new file mode 100644 index 00000000..ea53de5c Binary files /dev/null and b/deprecated_tests/sem02/tests/test_data/lesson05/diag_A_data.npy differ diff --git a/deprecated_tests/sem02/tests/test_data/lesson05/y_data.npy b/deprecated_tests/sem02/tests/test_data/lesson05/y_data.npy new file mode 100644 index 00000000..65637994 Binary files /dev/null and b/deprecated_tests/sem02/tests/test_data/lesson05/y_data.npy differ diff --git a/solutions/lesson12/__init__.py b/homeworks/sem01/hw1/__init__.py similarity index 100% rename from solutions/lesson12/__init__.py rename to homeworks/sem01/hw1/__init__.py diff --git a/homeworks/hw1/aggregate_segmentation.py b/homeworks/sem01/hw1/aggregate_segmentation.py similarity index 100% rename from homeworks/hw1/aggregate_segmentation.py rename to homeworks/sem01/hw1/aggregate_segmentation.py diff --git a/homeworks/hw1/backoff.py b/homeworks/sem01/hw1/backoff.py similarity index 100% rename from homeworks/hw1/backoff.py rename to homeworks/sem01/hw1/backoff.py diff --git a/homeworks/hw1/cache.py b/homeworks/sem01/hw1/cache.py similarity index 100% rename from homeworks/hw1/cache.py rename to homeworks/sem01/hw1/cache.py diff --git a/homeworks/hw1/convert_exception.py b/homeworks/sem01/hw1/convert_exception.py similarity index 100% rename from homeworks/hw1/convert_exception.py rename to homeworks/sem01/hw1/convert_exception.py diff --git a/homeworks/hw1/description.md b/homeworks/sem01/hw1/description.md similarity index 100% rename from homeworks/hw1/description.md rename to homeworks/sem01/hw1/description.md diff --git a/requirements-ci.txt b/requirements-ci.txt index 18dae9a6..581b008d 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -1,3 +1,7 @@ +matplotlib==3.8.0 +numpy==1.26.1 +pandas==2.2.2 + pytest==8.4.2 pytest-cov==7.0.0 ruff==0.13.0 diff --git a/tests_hw/__init__.py b/solutions/sem01/__init__.py similarity index 100% rename from tests_hw/__init__.py rename to solutions/sem01/__init__.py diff --git a/tests_hw/hw1_test_data/__init__.py b/solutions/sem01/lesson02/__init__.py similarity index 100% rename from tests_hw/hw1_test_data/__init__.py rename to solutions/sem01/lesson02/__init__.py diff --git a/solutions/lesson02/task1.py b/solutions/sem01/lesson02/task1.py similarity index 100% rename from solutions/lesson02/task1.py rename to solutions/sem01/lesson02/task1.py diff --git a/solutions/lesson02/task2.py b/solutions/sem01/lesson02/task2.py similarity index 100% rename from solutions/lesson02/task2.py rename to solutions/sem01/lesson02/task2.py diff --git a/solutions/lesson02/task3.py b/solutions/sem01/lesson02/task3.py similarity index 100% rename from solutions/lesson02/task3.py rename to solutions/sem01/lesson02/task3.py diff --git a/solutions/lesson02/task4.py b/solutions/sem01/lesson02/task4.py similarity index 100% rename from solutions/lesson02/task4.py rename to solutions/sem01/lesson02/task4.py diff --git a/solutions/lesson02/task5.py b/solutions/sem01/lesson02/task5.py similarity index 100% rename from solutions/lesson02/task5.py rename to solutions/sem01/lesson02/task5.py diff --git a/solutions/lesson02/task6.py b/solutions/sem01/lesson02/task6.py similarity index 100% rename from solutions/lesson02/task6.py rename to solutions/sem01/lesson02/task6.py diff --git a/solutions/lesson02/task7.py b/solutions/sem01/lesson02/task7.py similarity index 100% rename from solutions/lesson02/task7.py rename to solutions/sem01/lesson02/task7.py diff --git a/solutions/lesson03/task1.py b/solutions/sem01/lesson03/task1.py similarity index 100% rename from solutions/lesson03/task1.py rename to solutions/sem01/lesson03/task1.py diff --git a/solutions/lesson03/task2.py b/solutions/sem01/lesson03/task2.py similarity index 100% rename from solutions/lesson03/task2.py rename to solutions/sem01/lesson03/task2.py diff --git a/solutions/lesson03/task3.py b/solutions/sem01/lesson03/task3.py similarity index 100% rename from solutions/lesson03/task3.py rename to solutions/sem01/lesson03/task3.py diff --git a/solutions/lesson04/task1.py b/solutions/sem01/lesson04/task1.py similarity index 100% rename from solutions/lesson04/task1.py rename to solutions/sem01/lesson04/task1.py diff --git a/solutions/lesson04/task2.py b/solutions/sem01/lesson04/task2.py similarity index 100% rename from solutions/lesson04/task2.py rename to solutions/sem01/lesson04/task2.py diff --git a/solutions/lesson04/task3.py b/solutions/sem01/lesson04/task3.py similarity index 100% rename from solutions/lesson04/task3.py rename to solutions/sem01/lesson04/task3.py diff --git a/solutions/lesson04/task4.py b/solutions/sem01/lesson04/task4.py similarity index 100% rename from solutions/lesson04/task4.py rename to solutions/sem01/lesson04/task4.py diff --git a/solutions/lesson04/task5.py b/solutions/sem01/lesson04/task5.py similarity index 100% rename from solutions/lesson04/task5.py rename to solutions/sem01/lesson04/task5.py diff --git a/solutions/lesson04/task6.py b/solutions/sem01/lesson04/task6.py similarity index 100% rename from solutions/lesson04/task6.py rename to solutions/sem01/lesson04/task6.py diff --git a/solutions/sem01/lesson05/__init__.py b/solutions/sem01/lesson05/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/solutions/lesson05/task1.py b/solutions/sem01/lesson05/task1.py similarity index 100% rename from solutions/lesson05/task1.py rename to solutions/sem01/lesson05/task1.py diff --git a/solutions/lesson05/task2.py b/solutions/sem01/lesson05/task2.py similarity index 100% rename from solutions/lesson05/task2.py rename to solutions/sem01/lesson05/task2.py diff --git a/solutions/lesson05/task3.py b/solutions/sem01/lesson05/task3.py similarity index 100% rename from solutions/lesson05/task3.py rename to solutions/sem01/lesson05/task3.py diff --git a/solutions/lesson05/task4.py b/solutions/sem01/lesson05/task4.py similarity index 100% rename from solutions/lesson05/task4.py rename to solutions/sem01/lesson05/task4.py diff --git a/solutions/lesson05/task5.py b/solutions/sem01/lesson05/task5.py similarity index 100% rename from solutions/lesson05/task5.py rename to solutions/sem01/lesson05/task5.py diff --git a/solutions/lesson05/task6.py b/solutions/sem01/lesson05/task6.py similarity index 100% rename from solutions/lesson05/task6.py rename to solutions/sem01/lesson05/task6.py diff --git a/solutions/lesson06/task1.py b/solutions/sem01/lesson06/task1.py similarity index 100% rename from solutions/lesson06/task1.py rename to solutions/sem01/lesson06/task1.py diff --git a/solutions/lesson06/task2.py b/solutions/sem01/lesson06/task2.py similarity index 100% rename from solutions/lesson06/task2.py rename to solutions/sem01/lesson06/task2.py diff --git a/solutions/lesson06/task3.py b/solutions/sem01/lesson06/task3.py similarity index 100% rename from solutions/lesson06/task3.py rename to solutions/sem01/lesson06/task3.py diff --git a/solutions/lesson06/task4.py b/solutions/sem01/lesson06/task4.py similarity index 100% rename from solutions/lesson06/task4.py rename to solutions/sem01/lesson06/task4.py diff --git a/solutions/sem01/lesson08/__init__.py b/solutions/sem01/lesson08/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/solutions/lesson08/task1.py b/solutions/sem01/lesson08/task1.py similarity index 100% rename from solutions/lesson08/task1.py rename to solutions/sem01/lesson08/task1.py diff --git a/solutions/lesson08/task2.py b/solutions/sem01/lesson08/task2.py similarity index 100% rename from solutions/lesson08/task2.py rename to solutions/sem01/lesson08/task2.py diff --git a/solutions/sem01/lesson11/__init__.py b/solutions/sem01/lesson11/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/solutions/lesson11/task1.py b/solutions/sem01/lesson11/task1.py similarity index 100% rename from solutions/lesson11/task1.py rename to solutions/sem01/lesson11/task1.py diff --git a/solutions/sem01/lesson12/__init__.py b/solutions/sem01/lesson12/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/solutions/lesson12/task1.py b/solutions/sem01/lesson12/task1.py similarity index 100% rename from solutions/lesson12/task1.py rename to solutions/sem01/lesson12/task1.py diff --git a/solutions/lesson12/task2.py b/solutions/sem01/lesson12/task2.py similarity index 100% rename from solutions/lesson12/task2.py rename to solutions/sem01/lesson12/task2.py diff --git a/solutions/lesson12/task3.py b/solutions/sem01/lesson12/task3.py similarity index 100% rename from solutions/lesson12/task3.py rename to solutions/sem01/lesson12/task3.py diff --git a/solutions/sem02/__init__.py b/solutions/sem02/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/solutions/sem02/lesson03/__init__.py b/solutions/sem02/lesson03/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/solutions/sem02/lesson03/task1.py b/solutions/sem02/lesson03/task1.py new file mode 100644 index 00000000..2c3fc0b5 --- /dev/null +++ b/solutions/sem02/lesson03/task1.py @@ -0,0 +1,20 @@ +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def sum_arrays_vectorized( + lhs: np.ndarray, + rhs: np.ndarray, +) -> np.ndarray: ... + + +def compute_poly_vectorized(abscissa: np.ndarray) -> np.ndarray: ... + + +def get_mutual_l2_distances_vectorized( + lhs: np.ndarray, + rhs: np.ndarray, +) -> np.ndarray: ... diff --git a/solutions/sem02/lesson03/task2.py b/solutions/sem02/lesson03/task2.py new file mode 100644 index 00000000..fc823c1d --- /dev/null +++ b/solutions/sem02/lesson03/task2.py @@ -0,0 +1,19 @@ +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def convert_from_sphere( + distances: np.ndarray, + azimuth: np.ndarray, + inclination: np.ndarray, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ... + + +def convert_to_sphere( + abscissa: np.ndarray, + ordinates: np.ndarray, + applicates: np.ndarray, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: ... diff --git a/solutions/sem02/lesson03/task3.py b/solutions/sem02/lesson03/task3.py new file mode 100644 index 00000000..477acd0c --- /dev/null +++ b/solutions/sem02/lesson03/task3.py @@ -0,0 +1,6 @@ +import numpy as np + + +def get_extremum_indices( + ordinates: np.ndarray, +) -> tuple[np.ndarray, np.ndarray]: ... diff --git a/solutions/sem02/lesson04/__init__.py b/solutions/sem02/lesson04/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/solutions/sem02/lesson04/images/circle.jpg b/solutions/sem02/lesson04/images/circle.jpg new file mode 100644 index 00000000..6e1e9954 Binary files /dev/null and b/solutions/sem02/lesson04/images/circle.jpg differ diff --git a/solutions/sem02/lesson04/requirements.txt b/solutions/sem02/lesson04/requirements.txt new file mode 100644 index 00000000..039f1d50 --- /dev/null +++ b/solutions/sem02/lesson04/requirements.txt @@ -0,0 +1,3 @@ +matplotlib==3.8.0 +numpy==1.26.1 +opencv-python-headless==4.9.0.80 diff --git a/solutions/sem02/lesson04/task1.py b/solutions/sem02/lesson04/task1.py new file mode 100644 index 00000000..1b5526c1 --- /dev/null +++ b/solutions/sem02/lesson04/task1.py @@ -0,0 +1,27 @@ +import numpy as np + + +def pad_image(image: np.ndarray, pad_size: int) -> np.ndarray: + # ваш код + return image + + +def blur_image( + image: np.ndarray, + kernel_size: int, +) -> np.ndarray: + # ваш код + return image + + +if __name__ == "__main__": + import os + from pathlib import Path + + from utils.utils import compare_images, get_image + + current_directory = Path(__file__).resolve().parent + image = get_image(os.path.join(current_directory, "images", "circle.jpg")) + image_blured = blur_image(image, kernel_size=21) + + compare_images(image, image_blured) diff --git a/solutions/sem02/lesson04/task2.py b/solutions/sem02/lesson04/task2.py new file mode 100644 index 00000000..be9a2288 --- /dev/null +++ b/solutions/sem02/lesson04/task2.py @@ -0,0 +1,10 @@ +import numpy as np + + +def get_dominant_color_info( + image: np.ndarray[np.uint8], + threshold: int = 5, +) -> tuple[np.uint8, float]: + # ваш код + + return 0, 0 diff --git a/solutions/sem02/lesson04/utils/utils.py b/solutions/sem02/lesson04/utils/utils.py new file mode 100644 index 00000000..8a06872e --- /dev/null +++ b/solutions/sem02/lesson04/utils/utils.py @@ -0,0 +1,22 @@ +import cv2 as cv +import matplotlib.pyplot as plt +import numpy as np + + +def get_image(path_to_image: str) -> np.ndarray: + image = cv.imread(path_to_image) + return cv.cvtColor(image, code=cv.COLOR_BGR2RGB) + + +def compare_images(image1: np.ndarray, image2: np.ndarray) -> None: + _, (axis1, axis2) = plt.subplots(1, 2, figsize=(16, 8)) + axis1: plt.Axes = axis1 + axis2: plt.Axes = axis2 + + axis1.imshow(image1) + axis2.imshow(image2) + + axis1.axis("off") + axis2.axis("off") + + plt.show() diff --git a/solutions/sem02/lesson05/task1.py b/solutions/sem02/lesson05/task1.py new file mode 100644 index 00000000..e9c7c3c5 --- /dev/null +++ b/solutions/sem02/lesson05/task1.py @@ -0,0 +1,12 @@ +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def can_satisfy_demand( + costs: np.ndarray, + resource_amounts: np.ndarray, + demand_expected: np.ndarray, +) -> bool: ... diff --git a/solutions/sem02/lesson05/task2.py b/solutions/sem02/lesson05/task2.py new file mode 100644 index 00000000..be1fb9d2 --- /dev/null +++ b/solutions/sem02/lesson05/task2.py @@ -0,0 +1,11 @@ +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def get_projections_components( + matrix: np.ndarray, + vector: np.ndarray, +) -> tuple[np.ndarray | None, np.ndarray | None]: ... diff --git a/solutions/sem02/lesson05/task3.py b/solutions/sem02/lesson05/task3.py new file mode 100644 index 00000000..0c66906c --- /dev/null +++ b/solutions/sem02/lesson05/task3.py @@ -0,0 +1,12 @@ +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def adaptive_filter( + Vs: np.ndarray, + Vj: np.ndarray, + diag_A: np.ndarray, +) -> np.ndarray: ... diff --git a/solutions/sem02/lesson07/data/medic_data.json b/solutions/sem02/lesson07/data/medic_data.json new file mode 100644 index 00000000..ab77636f --- /dev/null +++ b/solutions/sem02/lesson07/data/medic_data.json @@ -0,0 +1,406 @@ +{ + "before": [ + "III", + "II", + "I", + "II", + "II", + "III", + "III", + "III", + "II", + "II", + "III", + "III", + "II", + "II", + "III", + "III", + "II", + "III", + "III", + "I", + "II", + "III", + "I", + "III", + "II", + "III", + "I", + "IV", + "II", + "IV", + "III", + "II", + "III", + "II", + "II", + "IV", + "II", + "II", + "II", + "IV", + "III", + "III", + "III", + "II", + "II", + "II", + "III", + "III", + "II", + "II", + "II", + "I", + "III", + "IV", + "IV", + "III", + "II", + "II", + "II", + "I", + "II", + "III", + "II", + "III", + "III", + "III", + "III", + "IV", + "II", + "II", + "IV", + "IV", + "III", + "II", + "III", + "III", + "II", + "II", + "II", + "IV", + "II", + "III", + "II", + "III", + "II", + "II", + "II", + "III", + "III", + "III", + "II", + "III", + "II", + "III", + "II", + "II", + "III", + "IV", + "I", + "I", + "III", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "III", + "II", + "II", + "II", + "I", + "II", + "IV", + "II", + "I", + "II", + "III", + "III", + "III", + "III", + "III", + "III", + "II", + "III", + "II", + "III", + "I", + "II", + "II", + "III", + "II", + "II", + "IV", + "II", + "III", + "I", + "III", + "I", + "III", + "III", + "II", + "II", + "III", + "III", + "IV", + "II", + "III", + "II", + "IV", + "II", + "I", + "II", + "II", + "IV", + "II", + "IV", + "III", + "IV", + "I", + "III", + "I", + "III", + "III", + "III", + "II", + "III", + "II", + "II", + "IV", + "II", + "II", + "I", + "II", + "II", + "III", + "II", + "III", + "II", + "II", + "II", + "II", + "II", + "III", + "III", + "II", + "II", + "II", + "IV", + "II", + "I", + "II", + "II", + "II", + "III", + "III", + "III" + ], + "after": [ + "I", + "II", + "II", + "II", + "I", + "I", + "II", + "IV", + "II", + "II", + "II", + "I", + "II", + "III", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "I", + "III", + "II", + "II", + "II", + "II", + "III", + "II", + "II", + "III", + "III", + "II", + "III", + "IV", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "IV", + "I", + "II", + "II", + "IV", + "I", + "III", + "I", + "IV", + "II", + "II", + "II", + "II", + "I", + "II", + "III", + "I", + "III", + "II", + "II", + "II", + "II", + "II", + "II", + "I", + "II", + "II", + "I", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "I", + "I", + "II", + "II", + "II", + "II", + "II", + "I", + "II", + "III", + "II", + "II", + "II", + "II", + "III", + "II", + "II", + "III", + "II", + "III", + "II", + "II", + "III", + "II", + "II", + "I", + "I", + "II", + "II", + "II", + "III", + "II", + "I", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "I", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "I", + "II", + "II", + "II", + "II", + "IV", + "III", + "II", + "II", + "III", + "II", + "II", + "IV", + "II", + "I", + "IV", + "III", + "II", + "III", + "II", + "I", + "I", + "II", + "II", + "II", + "III", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "III", + "II", + "II", + "II", + "III", + "I", + "II", + "II", + "I", + "II", + "II", + "II", + "IV", + "I", + "I", + "III", + "I", + "II", + "II", + "II", + "II", + "II", + "II", + "II", + "I" + ] +} \ No newline at end of file diff --git a/solutions/sem02/lesson07/task1.py b/solutions/sem02/lesson07/task1.py new file mode 100644 index 00000000..3a505d89 --- /dev/null +++ b/solutions/sem02/lesson07/task1.py @@ -0,0 +1,28 @@ +from typing import Any + +import matplotlib.pyplot as plt +import numpy as np + + +class ShapeMismatchError(Exception): + pass + + +def visualize_diagrams( + abscissa: np.ndarray, + ordinates: np.ndarray, + diagram_type: Any, +) -> None: + # ваш код + pass + + +if __name__ == "__main__": + mean = [2, 3] + cov = [[1, 1], [1, 2]] + space = 0.2 + + abscissa, ordinates = np.random.multivariate_normal(mean, cov, size=1000).T + + visualize_diagrams(abscissa, ordinates, "hist") + plt.show() diff --git a/solutions/sem02/lesson07/task2.py b/solutions/sem02/lesson07/task2.py new file mode 100644 index 00000000..decd607e --- /dev/null +++ b/solutions/sem02/lesson07/task2.py @@ -0,0 +1 @@ +# ваш код (используйте функции или классы для решения данной задачи) diff --git a/solutions/sem02/lesson08/data/maze.npy b/solutions/sem02/lesson08/data/maze.npy new file mode 100644 index 00000000..fe41116b Binary files /dev/null and b/solutions/sem02/lesson08/data/maze.npy differ diff --git a/solutions/sem02/lesson08/task1.py b/solutions/sem02/lesson08/task1.py new file mode 100644 index 00000000..89f88572 --- /dev/null +++ b/solutions/sem02/lesson08/task1.py @@ -0,0 +1,43 @@ +from functools import partial + +import matplotlib.pyplot as plt +import numpy as np + +from IPython.display import HTML +from matplotlib.animation import FuncAnimation + + +def create_modulation_animation( + modulation, + fc, + num_frames, + plot_duration, + time_step=0.001, + animation_step=0.01, + save_path="" +) -> FuncAnimation: + # ваш код + return FuncAnimation() + + +if __name__ == "__main__": + def modulation_function(t): + return np.cos(t * 6) + + num_frames = 100 + plot_duration = np.pi / 2 + time_step = 0.001 + animation_step = np.pi / 200 + fc = 50 + save_path_with_modulation = "modulated_signal.gif" + + animation = create_modulation_animation( + modulation=modulation_function, + fc=fc, + num_frames=num_frames, + plot_duration=plot_duration, + time_step=time_step, + animation_step=animation_step, + save_path=save_path_with_modulation + ) + HTML(animation.to_jshtml()) \ No newline at end of file diff --git a/solutions/sem02/lesson08/task2.py b/solutions/sem02/lesson08/task2.py new file mode 100644 index 00000000..b677c070 --- /dev/null +++ b/solutions/sem02/lesson08/task2.py @@ -0,0 +1,53 @@ +from functools import partial + +import matplotlib.pyplot as plt +import numpy as np + +from IPython.display import HTML +from matplotlib.animation import FuncAnimation + + + + +def animate_wave_algorithm( + maze: np.ndarray, + start: tuple[int, int], + end: tuple[int, int], + save_path: str = "" +) -> FuncAnimation: + # ваш код + return FuncAnimation() + +if __name__ == "__main__": + # Пример 1 + maze = np.array([ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 0], + [1, 1, 0, 1, 0, 1, 0], + [0, 0, 1, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0], + ]) + + start = (2, 0) + end = (5, 0) + save_path = "labyrinth.gif" # Укажите путь для сохранения анимации + + animation = animate_wave_algorithm(maze, start, end, save_path) + HTML(animation.to_jshtml()) + + # Пример 2 + + maze_path = "./data/maze.npy" + loaded_maze = np.load(maze_path) + + # можете поменять, если захотите запустить из других точек + start = (2, 0) + end = (5, 0) + loaded_save_path = "loaded_labyrinth.gif" + + loaded_animation = animate_wave_algorithm(loaded_maze, start, end, loaded_save_path) + HTML(loaded_animation.to_jshtml()) + + \ No newline at end of file