Labs and hws
502
hws/hw-1/README.md
Normal file
|
|
@ -0,0 +1,502 @@
|
||||||
|
Решите задачи, приведённые ниже. Код каждой задачи разместите в отдельном файле, название которого соответствует указанному в условии задачи. В качестве ответа на это задание загрузите архив (ZIP) с решениями. Архив должен содержать только файлы с решениями, файл test.py и вспомогательные файлы, необходимые для отдельных задач (не должно быть папок).
|
||||||
|
|
||||||
|
К заданию прилагается файл test.py (в архиве hhww1.zip) c заготовкой для написания тестов с использованием фреймворка pytest. Дополните его тестами для всех задач. Для каждой задачи достаточно рассмотреть принципиально различные ситуации (входные данные). Всего должно быть написано не менее 60 тестов (в прикреплённом файле приведено 8 тестов).
|
||||||
|
|
||||||
|
Для запуска тестов необходимо выполнить следующие шаги.
|
||||||
|
|
||||||
|
1. Создайте виртуальное окружение:
|
||||||
|
|
||||||
|
```
|
||||||
|
python -m venv ve
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Активируйте виртуальное окружение**:
|
||||||
|
|
||||||
|
- Команда для Windows: `ve\Scripts\activate`
|
||||||
|
- Команда для Linux: `source ve/bin/activate`
|
||||||
|
|
||||||
|
3. Установите pytest:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip install pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Запустите тесты
|
||||||
|
|
||||||
|
```
|
||||||
|
pytest test.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Если вы работаете в PowerShell и столкнулись с ошибкой "...Scripts\Activate.ps1 cannot be loaded because running scripts is disabled on this system." необходимо запустить PowerShell с правами администратора и выполнить команду Set-ExecutionPolicy RemoteSigned. Затем перезагрузить PowerShell и ошибка уйдет.
|
||||||
|
|
||||||
|
При необходимости замените значение переменной INTERPRETER на команду запуска интерпретатора, которая используется в вашей системе (например, python или py).
|
||||||
|
|
||||||
|
При обнаружении ошибок или неточностей в формулировках задач свяжитесь с преподавателем.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1) Скажи "Hello, World!" с помощью Python
|
||||||
|
|
||||||
|
Выведите "Hello, World!" на стандартный поток вывода.
|
||||||
|
|
||||||
|
**Название файла:** hello.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2) Python If-Else
|
||||||
|
|
||||||
|
Получив целое число (n), выполните следующие условные действия:
|
||||||
|
|
||||||
|
- Если n нечетное значение, выведите "Weird";
|
||||||
|
- Если n четно и находится в диапазоне от 2 до 5 включительно, выведите "Not Weird";
|
||||||
|
- Если n четно и находится в диапазоне от 6 до 20 включительно, выведите "Weird";
|
||||||
|
- Если n четно и больше 20, выведите "Not Weird".
|
||||||
|
|
||||||
|
**Формат ввода:** Строка, содержащая положительное целое число (n).
|
||||||
|
|
||||||
|
**Ограничения:** 1 ⩽ n ⩽ 100
|
||||||
|
|
||||||
|
**Название файла:** python_if_else.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3) Арифметические операторы
|
||||||
|
|
||||||
|
Считайте два целых числа из стандартного потока ввода (a и b), и напишите код для вывода трех строк, где:
|
||||||
|
|
||||||
|
1. Первая строка содержит сумму двух чисел.
|
||||||
|
2. Вторая строка содержит разницу двух чисел (первое - второе).
|
||||||
|
3. Третья строка содержит произведение двух чисел.
|
||||||
|
|
||||||
|
Например, для a = 3 и b = 5 программа должны вывести:
|
||||||
|
|
||||||
|
```
|
||||||
|
8
|
||||||
|
-2
|
||||||
|
15
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат ввода:** Первая строка содержит первое целое число (a), вторая строка -- второе целое число (b).
|
||||||
|
|
||||||
|
**Ограничения:** 1 ⩽ a ⩽ 10¹⁰ 1 ⩽ b ⩽ 10¹⁰
|
||||||
|
|
||||||
|
**Название файла:** arithmetic_operators.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4) Деление
|
||||||
|
|
||||||
|
Считайте два целых числа a и b из стандартного потока ввода.
|
||||||
|
|
||||||
|
Реализуйте логику для печати двух строк. Первая строка должна содержать результат целочисленного деления, (a // b). Вторая строка должна содержать результат деления с плавающей точкой (a/b).
|
||||||
|
|
||||||
|
Округление или форматирование не требуется. Ваша программа должна корректно отбрабатывать деление на ноль.
|
||||||
|
|
||||||
|
Например, при а = 3 и b = 5:
|
||||||
|
|
||||||
|
- Результат целочисленного деления 3 // 5 = 0.
|
||||||
|
- Результатом деления с плавающей точкой является 3 / 5 = 0.6
|
||||||
|
|
||||||
|
**Формат ввода:** Первая строка содержит первое целое число (a), вторая строка -- второе целое число (b).
|
||||||
|
|
||||||
|
**Название файла:** division.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5) Циклы
|
||||||
|
|
||||||
|
Считайте целое число n. Для всех неотрицательных целых чисел i < n выведите i².
|
||||||
|
|
||||||
|
Например, для n = 3 список неотрицательных целых чисел, которые меньше n, является [0,1,2]. Выведите квадрат каждого числа в отдельной строке:
|
||||||
|
|
||||||
|
```
|
||||||
|
0
|
||||||
|
1
|
||||||
|
4
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат ввода:** Строка, содержащая целое число (n).
|
||||||
|
|
||||||
|
**Ограничения:** 1 ⩽ n ⩽ 20
|
||||||
|
|
||||||
|
**Название файла:** loops.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6) Функция вывода
|
||||||
|
|
||||||
|
Считайте целое число n из стандартного потока ввода.
|
||||||
|
|
||||||
|
Без использования каких-либо строковых методов попробуйте вывести следующее: 123…n, где «…» означает последовательность числел между 3 и n.
|
||||||
|
|
||||||
|
Например, при n = 5 нужно выыести строку "12345".
|
||||||
|
|
||||||
|
**Формат ввода:** Строка, содержащая целое число (n).
|
||||||
|
|
||||||
|
**Ограничения:** 1 ⩽ n ⩽ 20
|
||||||
|
|
||||||
|
**Название файла:** print_function.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7) Узнай, кто занял второе место!
|
||||||
|
|
||||||
|
Учитывая таблицу результатов участников вашей университетской спартакиады, вам необходимо указать результат, занявший второе место. Вам даны быллы n участников. Запишите их в список и найдите оценку занявшего второе место.
|
||||||
|
|
||||||
|
**Формат ввода:** Первая строка содержит n. Вторая строка содержит массив A[] из n целых чисел, разделённых пробелом.
|
||||||
|
|
||||||
|
**Формат вывода:** Выведите результат, занявший второе место.
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
5
|
||||||
|
2 3 6 6 5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:** 5
|
||||||
|
|
||||||
|
**Объяснение** Дан список [2,3,6,6,5]. Максимальный балл равен 6, второй максимальный - 5. Следовательно, мы выводим 5 как результат, занявший второе место.
|
||||||
|
|
||||||
|
**Название файла:** second_score.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8) Вложенные списки
|
||||||
|
|
||||||
|
Учитывая имена и оценки каждого учащегося в классе, сохраните их во вложенном списке и выведите имя (имена) любого учащегося, имеющего вторую по величине оценку.
|
||||||
|
|
||||||
|
**Примечание:** Если есть несколько учеников со вторыми по величине оценками, расположите их имена в алфавитном порядке и выведите каждое имя с новой строки.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```python
|
||||||
|
records = [['chi', 20.0], ['beta', 50.0], ['alpha', 50.0]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Упорядоченный список оценок - [20.0,50.0], поэтому второй по величине балл - 50.0. Есть два студента с таким баллом: ['beta','alpha']. Имена в алфавитном порядке напечатаны как:
|
||||||
|
|
||||||
|
```
|
||||||
|
alpha
|
||||||
|
beta
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат ввода:** Первая строка содержит целое число (N) -- количество учащихся. В 2N последующих строках каждый учащийся описывается в 2 строках:
|
||||||
|
|
||||||
|
- В первой строке указано имя учащегося.
|
||||||
|
- Во второй строке указана его оценка.
|
||||||
|
|
||||||
|
**Формат вывода:** Выведите имя (имена) любого ученика (ов), имеющего вторую по величине оценку. Если учеников несколько, расположите их имена в алфавитном порядке и выведите каждое с новой строки.
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
5
|
||||||
|
Гарри
|
||||||
|
37.21
|
||||||
|
Берри
|
||||||
|
37.21
|
||||||
|
Тина
|
||||||
|
37.2
|
||||||
|
Акрити
|
||||||
|
41
|
||||||
|
Харш
|
||||||
|
39
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Берри
|
||||||
|
Гарри
|
||||||
|
```
|
||||||
|
|
||||||
|
**Объяснение** В этом классе есть 5 учеников, чьи имена и оценки собраны для составления следующего списка:
|
||||||
|
|
||||||
|
```python
|
||||||
|
students = [['Harry', 37.21], ['Berry', 37.21], ['Tina', 37.2], ['Akriti', 41], ['Harsh', 39]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Самая низкая оценка 37.2 принадлежит Тине. Вторая по величине оценка (37.21) принадлежит как Гарри, так и Берри, поэтому мы упорядочиваем их имена в алфавитном порядке и выводим каждое имя с новой строки.
|
||||||
|
|
||||||
|
**Ограничения:** 2 ⩽ N ⩽ 5
|
||||||
|
|
||||||
|
Во вводимых данных всегда должен присутствовать хотя бы один учащийся со второй по величине оценкой.
|
||||||
|
|
||||||
|
**Название файла:** nested_list.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9) Cписки
|
||||||
|
|
||||||
|
Рассмотрим список (arr = []). Вы можете выполнить следующие команды:
|
||||||
|
|
||||||
|
1. insert i e: вставить целое число e в позицию i.
|
||||||
|
2. print: вывести список.
|
||||||
|
3. remove e: удалить первое вхождение целого числа e.
|
||||||
|
4. append e: вставить целое число e в конце списка.
|
||||||
|
5. sort: сортировать список.
|
||||||
|
6. pop: удалить последний элемент из списка.
|
||||||
|
7. reverse: перевернуть список.
|
||||||
|
|
||||||
|
Инициализируйте свой список и считайте из стандартного потока ввода значение n, за которым следуют n строк команд, где каждая команда будет соответствовать одному из приведённых выше 7 типов. Пройдитесь по командам в порядке их ввода и выполните соответствующую операцию в вашем списке.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```
|
||||||
|
n = 4
|
||||||
|
append 1
|
||||||
|
append 2
|
||||||
|
insert 1 3
|
||||||
|
print
|
||||||
|
```
|
||||||
|
|
||||||
|
- append 1: Добавить 1 в список, arr=[1].
|
||||||
|
- append 2: Добавить 2 в список, arr=[1,2].
|
||||||
|
- insert 1 3: Вставить 3 по индексу 1, arr=[1,3,2].
|
||||||
|
- print: Вывести массив.
|
||||||
|
|
||||||
|
**Вывод:**
|
||||||
|
|
||||||
|
```
|
||||||
|
[1, 3, 2]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат ввода:** Первая строка содержит целое число n, обозначающее количество команд. Каждая строка i из n последующих строк содержит одну из команд, описанных выше.
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
|
||||||
|
- Элементы, добавляемые в список, должны быть целыми числами.
|
||||||
|
|
||||||
|
**Формат вывода:** Для каждой команды типа print выведите список с новой строки.
|
||||||
|
|
||||||
|
**Пример ввода**
|
||||||
|
|
||||||
|
```
|
||||||
|
12
|
||||||
|
insert 0 5
|
||||||
|
insert 1 10
|
||||||
|
insert 0 6
|
||||||
|
print
|
||||||
|
remove 6
|
||||||
|
append 9
|
||||||
|
append 1
|
||||||
|
sort
|
||||||
|
print
|
||||||
|
pop
|
||||||
|
reverse
|
||||||
|
print
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода**
|
||||||
|
|
||||||
|
```
|
||||||
|
[6, 5, 10]
|
||||||
|
[1, 5, 9, 10]
|
||||||
|
[9, 5, 1]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** lists.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10) Пример обмена
|
||||||
|
|
||||||
|
Вам дается строка, и ваша задача - поменять регистры местами. Другими словами, преобразуйте все строчные буквы в прописные и наоборот.
|
||||||
|
|
||||||
|
Например:
|
||||||
|
|
||||||
|
```
|
||||||
|
Www.MosPolytech.ru → wWW.mOSpOLYTECH.ru
|
||||||
|
Pythonist 2 → pYTHONIST 2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Формат ввода:** Строка (s), содержащая некоторую последовательность символов.
|
||||||
|
|
||||||
|
**Ограничения:** 0 < len(s) ⩽ 1000
|
||||||
|
|
||||||
|
**Название файла:** swap_case.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11) Разделение и объединение строк
|
||||||
|
|
||||||
|
Вам через стандартный поток ввода будет предоставлена строка. Разделите строку по пробелу и соедините получившиеся фрагменты с помощью дефиса.
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
this is a string
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
this-is-a-string
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** split_and_join.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12) Поиск слова максимальной длины
|
||||||
|
|
||||||
|
Напишите программу, которая считывает содержимое файла example.txt (приложен к заданию) и выводит на стандартный поток вывода слово максимальной длины, встречающееся в этом файле. Если таких слов несколько, программа должна вывести все. При подсчёте длины слова не должны учитываться знаки препинания, кавычки и прочие спецсимволы.
|
||||||
|
|
||||||
|
**Формат вывода:** Строка, содержащая слово максимальной длины (если таких слов несколько -- каждое на отдельной строке в порядке их встречаемости в тексте).
|
||||||
|
|
||||||
|
**Название файла:** max_word.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13) Агрегирование данных
|
||||||
|
|
||||||
|
В файле products.csv (приложен к заданию) приведены данные ежемесячных трат на продукты для различных категорий потребителей. Напишите программу, которая считывает данные из файла и вычисляет суммарные затраты для каждой категории потребителей (взрослый, пенсионер, ребёнок). Полученные значения выведите на стандартный поток вывода.
|
||||||
|
|
||||||
|
**Формат вывода:** Три числа разделённых пробелами -- суммарные затраты для каждой категории потребителей. Каждое число должно быть округлено до двух знаков после десятичного разделителя.
|
||||||
|
|
||||||
|
**Название файла:** price_sum.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14) Анаграммы
|
||||||
|
|
||||||
|
Даны две строки. Определите, можно ли получить одну из другой перестановкой букв.
|
||||||
|
|
||||||
|
**Формат ввода:** Программа получает на вход две строки, содержащие только ASCII-символы, не содержащие пробелы.
|
||||||
|
|
||||||
|
**Формат вывода:** Программа должна вывести слово YES, если одна строка может быть получена из другой перестановкой букв или NO.
|
||||||
|
|
||||||
|
**Название файла:** anagram.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15) Пассажиры в метро
|
||||||
|
|
||||||
|
Для изучения пассажиропотока в метро было записано время входа и время выхода в метро каждого пассажира. На основании этих данных определите, сколько пассажиров было в метро в некоторый заданный момент времени T.
|
||||||
|
|
||||||
|
**Формат ввода:** Программа получает на вход число пассажиров N. Далее в N строках записано время входа (Aᵢ) и время выхода (Bᵢ) каждого пассажира (Aᵢ < Bᵢ). Время задаётся в виде целого числа -- количества минут от начала работы метрополитена. Значения времени входа и времени выхода разделены пробелом. В следующей строке дано время T.
|
||||||
|
|
||||||
|
**Формат вывода:** Выведите одно число: количество пассажиров в момент времени T. Если какой-то пассажир в момент T входит или выходит, то его тоже необходимо посчитать.
|
||||||
|
|
||||||
|
**Название файла:** metro.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16) Игра миньонов
|
||||||
|
|
||||||
|
Кевин и Стюарт хотят сыграть в «Игру миньонов».
|
||||||
|
|
||||||
|
**Правила игры** Обоим игрокам даётся одна и та же строка. Оба игрока должны составить подстроки, используя буквы строки. Стюарт должен составить слова, начинающиеся с согласных. Кевину нужно составить слова, начинающиеся с гласных. Игра заканчивается, когда оба игрока составили все возможные подстроки.
|
||||||
|
|
||||||
|
**Подсчет очков** Игрок получает +1 очко за каждое вхождение подстроки в строку.
|
||||||
|
|
||||||
|
Например: Строка = БАНАНА Начальное гласное слово Кевина = АНА. Здесь АНА встречается дважды в БАНАНА. Следовательно, Кевин получит 2 очка.
|
||||||
|
|
||||||
|
Для лучшего понимания посмотрите изображение ниже:
|
||||||
|
|
||||||
|
Ваша задача — определить победителя игры и его счёт.
|
||||||
|
|
||||||
|
**Формат ввода:** Одна строка ввода, содержащая строку S.
|
||||||
|
|
||||||
|
**Примечание.** Строка будет содержать только буквы верхнего регистра: [A-Z].
|
||||||
|
|
||||||
|
**Формат вывода:** Одна строка, содержащая имя победителя (Кевин или Стюарт) и набранное им количество очков (значения должны быть разделены пробелом).
|
||||||
|
|
||||||
|
**Ограничения:** 0 < len(S) ⩽ 10⁶
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
BANANA
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Стюарт 12
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** minion_game.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17) Високосный год
|
||||||
|
|
||||||
|
Почти каждые четыре года в календарь добавляется дополнительный день — 29 февраля, и этот день называется високосным. Он вносит в календарь поправку на тот факт, что наша планета совершает оборот вокруг Солнца примерно за 365,25 дней.
|
||||||
|
|
||||||
|
В григорианском календаре год является високосным, если он соответствует следующим правилам:
|
||||||
|
|
||||||
|
- Годы, делящиеся на 100 без остатка, не являются високосными, за исключением годов, которые делятся на 400 без остатка. Например, 1900 год не является високосным, а 2000 год — является.
|
||||||
|
- Годы делящиеся на 4 без остатка (например, 2016, 2024), являются високосными.
|
||||||
|
|
||||||
|
Это означает, что в григорианском календаре 2000 и 2400 годы являются високосными, а 1800, 1900, 2100, 2200, 2300 и 2500 НЕ являются високосными.(https://www.timeanddate.com/date/leapyear.html)
|
||||||
|
|
||||||
|
**Задача** Напишите функцию, позволяющую определить, является ли год високосным. Если это високосный год, верните логическое значение True, в противном случае верните False.
|
||||||
|
|
||||||
|
Ваша программа должна считывать год для проверки из стандартного потока ввода.
|
||||||
|
|
||||||
|
**Формат ввода:** Строка, содержащая год для проверки (year).
|
||||||
|
|
||||||
|
**Ограничения:** 1900 ⩽ year ⩽ 10⁵
|
||||||
|
|
||||||
|
**Выходной формат:** Результат вызова функции (True/False).
|
||||||
|
|
||||||
|
**Название файла:** is_leap.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18) Предпочтения
|
||||||
|
|
||||||
|
Вам дан массив из n целых чисел. Существуют также 2 непересекающиеся множества A и B, каждое из которых содержит m целых чисел. Вам нравятся все числа в наборе A и не нравятся все числа в наборе B. Ваше первоначальное настроение равно 0. Для каждого целого числа i в массиве, если i ∈ A, ваше настроение повышается на 1. Если i ∈ B, ваше настроение уменьшается на 1. В противном случае ваше настроение не изменится. В конце выведите своё окончательное настроение.
|
||||||
|
|
||||||
|
**Примечание.** Поскольку A и B являются множествами, они не содержат повторяющихся элементов. Однако массив может содержать повторяющиеся элементы.
|
||||||
|
|
||||||
|
**Ограничения:** 1 ⩽ n ⩽ 10⁵ 1 ⩽ m ⩽ 10⁵ 1 ⩽ i ⩽ 10⁹
|
||||||
|
|
||||||
|
**Формат ввода:** Первая строка содержит целые числа n и m, разделённые пробелом. Вторая строка содержит n целых чисел — элементы массива. Третья и четвёртая строки содержат по m целых чисел, входящих в множества A и B соответственно.
|
||||||
|
|
||||||
|
**Формат вывода:** Выведите одно целое число — ваше общее настроение.
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
3 2
|
||||||
|
1 5 3
|
||||||
|
3 1
|
||||||
|
5 7
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** happiness.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19) Пиратский корабль
|
||||||
|
|
||||||
|
Капитан пиратского корабля награбил больше, чем рассчитывал, а грузоподъёмность его судна ограничена. Помогите ему найти комбинацию наиболее ценных предметов с учётом того, что на корабль можно загружать не только целые предметы, но и их части.
|
||||||
|
|
||||||
|
**Формат ввода:** Первая строка содержит целые числа n и m, разделённые пробелом (n -- грузоподъёмность судна, m -- количество наименований грузов). Далее идёт m срок, в каждой из которых содержится по три значения: название груза, вес (целое число), стоимость (целое число).
|
||||||
|
|
||||||
|
**Формат вывода:** Выведите в порядке убывания стоимости список грузов, которые нужно погрузить на корабль. Каждый груз выведите в отдельной строке в формате "название вес стоимость" (вес и стоимость должны быть указаны с учётом ограничения грузоподъёмности судна, в случае дробных значений округлите до второго знака после десятичного разделителя).
|
||||||
|
|
||||||
|
**Название файла:** pirate_ship.py
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 20) Произведение матриц
|
||||||
|
|
||||||
|
Напишите функцию, которая вычисляет произведение двух квадратных матриц (A и B). При реализации нельзя использовать сторонние библиотеки (NumPy и т. д.).
|
||||||
|
|
||||||
|
**Формат ввода:** Первая строка содержит целое число n -- размерность матрицы. Далее идут 2n строк, содержащие по n целых чисел, разделённых пробелами, -- содержимое матриц (первые n строк относятся к матрице A, остальные -- к матрице B).
|
||||||
|
|
||||||
|
**Формат вывода:** Произведение матриц A и B (n строк по n целых чисел, разделённых пробелами).
|
||||||
|
|
||||||
|
**Ограничения:** 2 ⩽ n ⩽ 10
|
||||||
|
|
||||||
|
**Название файла:** matrix_mult.py
|
||||||
10
hws/hw-1/anagram.py
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
s1 = input()
|
||||||
|
s2 = input()
|
||||||
|
|
||||||
|
if s1.isascii() and s2.isascii() and ' ' not in s1 and ' ' not in s2:
|
||||||
|
if sorted(s1) == sorted(s2):
|
||||||
|
print("YES")
|
||||||
|
else:
|
||||||
|
print("NO")
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
8
hws/hw-1/arithmetic_operators.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
a = float(input())
|
||||||
|
b = float(input())
|
||||||
|
if 1 <= a <= 10 ** 10 and 1 <= b <= 10 ** 10:
|
||||||
|
print(a + b)
|
||||||
|
print(a - b)
|
||||||
|
print(a * b)
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
7
hws/hw-1/division.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
a = int(input())
|
||||||
|
b = int(input())
|
||||||
|
if b == 0:
|
||||||
|
print("Error: Division by zero")
|
||||||
|
else:
|
||||||
|
print(a // b)
|
||||||
|
print(a / b)
|
||||||
17
hws/hw-1/example.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
В Гороховой улице, в одном из больших домов, народонаселения которого стало бы на
|
||||||
|
целый уездный город, лежал утром в постели, на своей квартире, Илья Ильич Обломов.
|
||||||
|
|
||||||
|
Это был человек лет тридцати двух-трех от роду, среднего роста, приятной наружности,
|
||||||
|
с темно-серыми глазами, но с отсутствием всякой определенной идеи, всякой сосредоточенности
|
||||||
|
в чертах лица. Мысль гуляла вольной птицей по лицу, порхала в глазах, садилась на
|
||||||
|
полуотворенные губы, пряталась в складках лба, потом совсем пропадала, и тогда
|
||||||
|
во всем лице теплился ровный свет беспечности. С лица беспечность переходила в
|
||||||
|
позы всего тела, даже в складки шлафрока.
|
||||||
|
|
||||||
|
Иногда взгляд его помрачался выражением будто усталости или скуки; но ни
|
||||||
|
усталость, ни скука не могли ни на минуту согнать с лица мягкость, которая была
|
||||||
|
господствующим и основным выражением, не лица только, а всей души; а душа так
|
||||||
|
открыто и ясно светилась в глазах, в улыбке, в каждом движении головы, руки. И
|
||||||
|
поверхностно наблюдательный, холодный человек, взглянув мимоходом на Обломова,
|
||||||
|
сказал бы: «Добряк должен быть, простота!» Человек поглубже и посимпатичнее,
|
||||||
|
долго вглядываясь в лицо его, отошел бы в приятном раздумье, с улыбкой.
|
||||||
19
hws/hw-1/happiness.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
n, m = map(int, input().split())
|
||||||
|
if 1 <= n <= 10**5 and 1 <= m <= 10**5:
|
||||||
|
arr = list(map(int, input().split()))
|
||||||
|
A = set(map(int, input().split()))
|
||||||
|
B = set(map(int, input().split()))
|
||||||
|
|
||||||
|
if all(1 <= i <= 10**9 for i in arr):
|
||||||
|
happiness = 0
|
||||||
|
for i in arr:
|
||||||
|
if i in A:
|
||||||
|
happiness += 1
|
||||||
|
elif i in B:
|
||||||
|
happiness -= 1
|
||||||
|
|
||||||
|
print(happiness)
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
1
hws/hw-1/hello.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
print('Hello, world!')
|
||||||
12
hws/hw-1/is_leap.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
year = int(input())
|
||||||
|
if 1900 <= year <= 10**5:
|
||||||
|
if year % 400 == 0:
|
||||||
|
print(True)
|
||||||
|
elif year % 100 == 0:
|
||||||
|
print(False)
|
||||||
|
elif year % 4 == 0:
|
||||||
|
print(True)
|
||||||
|
else:
|
||||||
|
print(False)
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
22
hws/hw-1/lists.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
n = int(input())
|
||||||
|
arr = []
|
||||||
|
try:
|
||||||
|
for _ in range(n):
|
||||||
|
command = input().split()
|
||||||
|
cmd = command[0]
|
||||||
|
if cmd == "insert":
|
||||||
|
arr.insert(int(command[1]), int(command[2]))
|
||||||
|
elif cmd == "print":
|
||||||
|
print(arr)
|
||||||
|
elif cmd == "remove":
|
||||||
|
arr.remove(int(command[1]))
|
||||||
|
elif cmd == "append":
|
||||||
|
arr.append(int(command[1]))
|
||||||
|
elif cmd == "sort":
|
||||||
|
arr.sort()
|
||||||
|
elif cmd == "pop":
|
||||||
|
arr.pop()
|
||||||
|
elif cmd == "reverse":
|
||||||
|
arr.reverse()
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
print("Error")
|
||||||
6
hws/hw-1/loops.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
n = int(input())
|
||||||
|
if 1 <= n <= 20:
|
||||||
|
for i in range(n):
|
||||||
|
print(i ** 2)
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
20
hws/hw-1/matrix_mult.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
n = int(input())
|
||||||
|
if 2 <= n <= 10:
|
||||||
|
A = []
|
||||||
|
for _ in range(n):
|
||||||
|
A.append(list(map(int, input().split())))
|
||||||
|
|
||||||
|
B = []
|
||||||
|
for _ in range(n):
|
||||||
|
B.append(list(map(int, input().split())))
|
||||||
|
|
||||||
|
result = [[0] * n for _ in range(n)]
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(n):
|
||||||
|
for k in range(n):
|
||||||
|
result[i][j] += A[i][k] * B[k][j]
|
||||||
|
|
||||||
|
for row in result:
|
||||||
|
print(' '.join(map(str, row)))
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
14
hws/hw-1/max_word.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
with open('example.txt', 'r', encoding='utf-8') as f:
|
||||||
|
text = f.read()
|
||||||
|
|
||||||
|
words = re.findall(r'[а-яА-ЯёЁa-zA-Z]+', text)
|
||||||
|
max_len = max(len(w) for w in words)
|
||||||
|
result = []
|
||||||
|
for w in words:
|
||||||
|
if len(w) == max_len and w not in result:
|
||||||
|
result.append(w)
|
||||||
|
|
||||||
|
for w in result:
|
||||||
|
print(w)
|
||||||
23
hws/hw-1/metro.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
n = int(input())
|
||||||
|
passengers = []
|
||||||
|
valid = True
|
||||||
|
for _ in range(n):
|
||||||
|
a, b = map(int, input().split())
|
||||||
|
|
||||||
|
if a < b:
|
||||||
|
passengers.append((a, b))
|
||||||
|
else:
|
||||||
|
valid = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if valid and passengers:
|
||||||
|
t = int(input())
|
||||||
|
count = 0
|
||||||
|
for a, b in passengers:
|
||||||
|
if a <= t <= b:
|
||||||
|
count += 1
|
||||||
|
print(count)
|
||||||
|
else:
|
||||||
|
if not valid:
|
||||||
|
t = input()
|
||||||
|
print("Error")
|
||||||
20
hws/hw-1/minion_game.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
s = input()
|
||||||
|
if 0 < len(s) <= 10**6:
|
||||||
|
vowels = "AEIOUY"
|
||||||
|
kevin_score = 0
|
||||||
|
stuart_score = 0
|
||||||
|
|
||||||
|
for i in range(len(s)):
|
||||||
|
if s[i] in vowels:
|
||||||
|
kevin_score += len(s) - i
|
||||||
|
else:
|
||||||
|
stuart_score += len(s) - i
|
||||||
|
|
||||||
|
if kevin_score > stuart_score:
|
||||||
|
print(f"Kevin {kevin_score}")
|
||||||
|
elif stuart_score > kevin_score:
|
||||||
|
print(f"Stuart {stuart_score}")
|
||||||
|
else:
|
||||||
|
print("Draw")
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
16
hws/hw-1/nested_list.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
n = int(input())
|
||||||
|
if 2 <= n <= 5:
|
||||||
|
students = []
|
||||||
|
for _ in range(n):
|
||||||
|
name = input()
|
||||||
|
score = float(input())
|
||||||
|
students.append([name, score])
|
||||||
|
|
||||||
|
scores = sorted(set([s[1] for s in students]))
|
||||||
|
second_lowest = scores[1]
|
||||||
|
|
||||||
|
names = sorted([s[0] for s in students if s[1] == second_lowest])
|
||||||
|
for name in names:
|
||||||
|
print(name)
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
23
hws/hw-1/pirate_ship.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
n, m = map(int, input().split())
|
||||||
|
items = []
|
||||||
|
for _ in range(m):
|
||||||
|
parts = input().split()
|
||||||
|
name = parts[0]
|
||||||
|
weight = int(parts[1])
|
||||||
|
value = int(parts[2])
|
||||||
|
items.append((name, weight, value, value / weight))
|
||||||
|
|
||||||
|
items.sort(key=lambda x: x[3], reverse=True)
|
||||||
|
|
||||||
|
remaining = n
|
||||||
|
for name, weight, value, ratio in items:
|
||||||
|
if remaining == 0:
|
||||||
|
break
|
||||||
|
if weight <= remaining:
|
||||||
|
print(f"{name} {weight} {value}")
|
||||||
|
remaining -= weight
|
||||||
|
else:
|
||||||
|
new_weight = remaining
|
||||||
|
new_value = round(ratio * remaining, 2)
|
||||||
|
print(f"{name} {new_weight} {new_value}")
|
||||||
|
remaining = 0
|
||||||
14
hws/hw-1/price_sum.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import csv
|
||||||
|
|
||||||
|
adult_sum = 0
|
||||||
|
senior_sum = 0
|
||||||
|
child_sum = 0
|
||||||
|
|
||||||
|
with open('products.csv', 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
adult_sum += float(row['Взрослый'])
|
||||||
|
senior_sum += float(row['Пенсионер'])
|
||||||
|
child_sum += float(row['Ребенок'])
|
||||||
|
|
||||||
|
print(f"{adult_sum:.2f} {senior_sum:.2f} {child_sum:.2f}")
|
||||||
8
hws/hw-1/print_function.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
n = int(input())
|
||||||
|
if 1 <= n <= 20:
|
||||||
|
result = ""
|
||||||
|
for i in range(1, n + 1):
|
||||||
|
result += str(i)
|
||||||
|
print(result)
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
34
hws/hw-1/products.csv
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
Продукт,Взрослый,Пенсионер,Ребенок
|
||||||
|
говядина,878.66,814.37,754.37
|
||||||
|
молоко,744.90,651.79,852.91
|
||||||
|
Фрукты свежие (Яблоки),588.45,441.34,1158.27
|
||||||
|
хлеб пшеничный,510.95,482.03,358.18
|
||||||
|
мясо птицы,502.28,482.96,428.87
|
||||||
|
творог,477.17,409.00,477.17
|
||||||
|
хлеб ржаной,426.09,216.45,155.84
|
||||||
|
рыба свежая,325.72,279.19,327.58
|
||||||
|
свинина,300.76,257.79,105.98
|
||||||
|
сыр,288.41,256.36,288.41
|
||||||
|
масло сливочное,232.62,211.48,387.70
|
||||||
|
Картофель,230.67,183.80,202.41
|
||||||
|
Яйца (штук),187.25,178.33,179.23
|
||||||
|
сахар,139.05,130.43,115.05
|
||||||
|
столовые корнеплоды,125.64,109.76,123.04
|
||||||
|
капуста свежая и квашеная,114.48,94.74,92.63
|
||||||
|
масло растительное,102.80,86.57,48.69
|
||||||
|
баранина,83.66,52.29,0.00
|
||||||
|
макаронные изделия,81.38,72.33,63.29
|
||||||
|
прочие овощи (морковь),78.14,69.15,76.41
|
||||||
|
огурцы и помидоры свежие и соленые,71.38,57.11,179.89
|
||||||
|
чай,51.22,51.22,40.97
|
||||||
|
сметана,46.54,46.54,69.82
|
||||||
|
другие крупы (кроме риса),40.77,37.06,44.48
|
||||||
|
конфеты,39.17,39.17,78.33
|
||||||
|
рис,31.23,31.23,41.64
|
||||||
|
маргарин и другие жиры,29.58,39.43,9.86
|
||||||
|
мука пшеничная,24.83,24.83,20.69
|
||||||
|
бобовые,21.88,18.23,10.94
|
||||||
|
специи,21.85,21.85,21.85
|
||||||
|
печенье,21.24,21.24,74.35
|
||||||
|
сельдь,18.33,18.33,18.33
|
||||||
|
соль,5.74,4.66,3.72
|
||||||
|
12
hws/hw-1/python_if_else.py
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
n = int(input())
|
||||||
|
if 1 <= n <= 100:
|
||||||
|
if n % 2 == 1:
|
||||||
|
print("Weird")
|
||||||
|
elif n % 2 == 0 and n >= 2 and n <= 5:
|
||||||
|
print("Not Weird")
|
||||||
|
elif n % 2 == 0 and n >= 6 and n <= 20:
|
||||||
|
print("Weird")
|
||||||
|
else:
|
||||||
|
print("Not Weird")
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
5
hws/hw-1/second_score.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
n = int(input())
|
||||||
|
scores = list(map(int, input().split()))
|
||||||
|
max_score = max(scores)
|
||||||
|
second_max = max([s for s in scores if s < max_score])
|
||||||
|
print(second_max)
|
||||||
2
hws/hw-1/split_and_join.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
s = input()
|
||||||
|
print('-'.join(s.split()))
|
||||||
13
hws/hw-1/swap_case.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
s = input()
|
||||||
|
if 0 < len(s) <= 1000:
|
||||||
|
result = ""
|
||||||
|
for c in s:
|
||||||
|
if c.isupper():
|
||||||
|
result += c.lower()
|
||||||
|
elif c.islower():
|
||||||
|
result += c.upper()
|
||||||
|
else:
|
||||||
|
result += c
|
||||||
|
print(result)
|
||||||
|
else:
|
||||||
|
print("Error")
|
||||||
323
hws/hw-1/test.py
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
import subprocess
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
INTERPRETER = 'python'
|
||||||
|
|
||||||
|
def run_script(filename, input_data=None):
|
||||||
|
proc = subprocess.run(
|
||||||
|
[INTERPRETER, filename],
|
||||||
|
input='\n'.join(input_data if input_data else []),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False
|
||||||
|
)
|
||||||
|
return proc.stdout.strip()
|
||||||
|
|
||||||
|
test_data = {
|
||||||
|
'python_if_else': [
|
||||||
|
('1', 'Weird'), # граница: минимум n=1
|
||||||
|
('4', 'Not Weird'),
|
||||||
|
('3', 'Weird'),
|
||||||
|
('6','Weird'),
|
||||||
|
('22', 'Not Weird'),
|
||||||
|
('2', 'Not Weird'),
|
||||||
|
('5', 'Weird'),
|
||||||
|
('7', 'Weird'),
|
||||||
|
('19', 'Weird'),
|
||||||
|
('20', 'Weird'),
|
||||||
|
('21', 'Weird'),
|
||||||
|
('100', 'Not Weird') # граница: максимум n=100
|
||||||
|
],
|
||||||
|
'arithmetic_operators': [
|
||||||
|
(['1', '1'], ['2.0', '0.0', '1.0']), # граница: минимум a=1, b=1
|
||||||
|
(['1', '2'], ['3.0', '-1.0', '2.0']),
|
||||||
|
(['10', '5'], ['15.0', '5.0', '50.0']),
|
||||||
|
(['10000000000', '10000000000'], ['20000000000.0', '0.0', '1e+20']), # граница: максимум a=10^10, b=10^10
|
||||||
|
(['100', '50'], ['150.0', '50.0', '5000.0']),
|
||||||
|
(['1', '10000000000'], ['10000000001.0', '-9999999999.0', '10000000000.0']) # граница: проверка диапазона
|
||||||
|
],
|
||||||
|
'division': [
|
||||||
|
(['3', '5'], ['0', '0.6']),
|
||||||
|
(['10', '2'], ['5', '5.0']),
|
||||||
|
(['7', '3'], ['2', '2.3333333333333335']),
|
||||||
|
(['100', '10'], ['10', '10.0'])
|
||||||
|
],
|
||||||
|
'loops': [
|
||||||
|
(['1'], ['0']), # граница: минимум n=1
|
||||||
|
(['3'], ['0', '1', '4']),
|
||||||
|
(['5'], ['0', '1', '4', '9', '16']),
|
||||||
|
(['10'], ['0', '1', '4', '9', '16', '25', '36', '49', '64', '81']),
|
||||||
|
(['20'], ['0', '1', '4', '9', '16', '25', '36', '49', '64', '81', '100', '121', '144', '169', '196', '225', '256', '289', '324', '361']) # граница: максимум n=20
|
||||||
|
],
|
||||||
|
'print_function': [
|
||||||
|
(['1'], ['1']), # граница: минимум n=1
|
||||||
|
(['5'], ['12345']),
|
||||||
|
(['10'], ['12345678910']),
|
||||||
|
(['15'], ['123456789101112131415']),
|
||||||
|
(['20'], ['1234567891011121314151617181920']) # граница: максимум n=20
|
||||||
|
],
|
||||||
|
'second_score': [
|
||||||
|
(['5', '2 3 6 6 5'], ['5']),
|
||||||
|
(['4', '10 20 30 40'], ['30']),
|
||||||
|
(['3', '1 1 2'], ['1']),
|
||||||
|
(['6', '5 5 5 10 10 15'], ['10'])
|
||||||
|
],
|
||||||
|
'nested_list': [
|
||||||
|
(['2', 'Harry', '37.21', 'Berry', '37.2'], ['Harry']), # граница: минимум N=2
|
||||||
|
(['3', 'Alice', '50', 'Bob', '45', 'Charlie', '45'], ['Alice']),
|
||||||
|
(['5', 'Гарри', '37.21', 'Берри', '37.21', 'Тина', '37.2', 'Акрити', '41', 'Харш', '39'], ['Берри', 'Гарри']), # граница: максимум N=5
|
||||||
|
(['3', 'Ann', '10', 'Bob', '20', 'Carl', '20'], ['Bob', 'Carl']) # несколько студентов со вторым баллом
|
||||||
|
],
|
||||||
|
'swap_case': [
|
||||||
|
(['a'], ['A']), # граница: минимальная длина len(s)=1
|
||||||
|
(['Hello'], ['hELLO']),
|
||||||
|
(['Www.MosPolytech.ru'], ['wWW.mOSpOLYTECH.RU']),
|
||||||
|
(['Pythonist 2'], ['pYTHONIST 2']),
|
||||||
|
(['ABC123xyz'], ['abc123XYZ']),
|
||||||
|
(['a' * 1000], ['A' * 1000]) # граница: максимальная длина len(s)=1000
|
||||||
|
],
|
||||||
|
'split_and_join': [
|
||||||
|
(['this is a string'], ['this-is-a-string']),
|
||||||
|
(['hello world'], ['hello-world']),
|
||||||
|
(['one two three four'], ['one-two-three-four'])
|
||||||
|
],
|
||||||
|
'anagram': [
|
||||||
|
(['abc', 'bca'], ['YES']),
|
||||||
|
(['abc', 'def'], ['NO']),
|
||||||
|
(['listen', 'silent'], ['YES']),
|
||||||
|
(['hello', 'world'], ['NO']),
|
||||||
|
(['test', 'sett'], ['YES'])
|
||||||
|
],
|
||||||
|
'is_leap': [
|
||||||
|
(['1900'], ['False']), # граница: минимум year=1900
|
||||||
|
(['2000'], ['True']), # делится на 400
|
||||||
|
(['1900'], ['False']), # делится на 100, но не на 400
|
||||||
|
(['2016'], ['True']), # делится на 4, но не на 100
|
||||||
|
(['2017'], ['False']), # не делится на 4
|
||||||
|
(['2400'], ['True']), # делится на 400
|
||||||
|
(['2100'], ['False']), # делится на 100, но не на 400
|
||||||
|
(['2020'], ['True']),
|
||||||
|
(['2024'], ['True']),
|
||||||
|
(['100000'], ['True']) # граница: максимум year=10^5, делится на 400
|
||||||
|
],
|
||||||
|
'happiness': [
|
||||||
|
(['1 1', '1', '1', '2'], ['1']), # граница: минимум n=1, m=1
|
||||||
|
(['3 2', '1 5 3', '3 1', '5 7'], ['1']),
|
||||||
|
(['5 3', '1 2 3 4 5', '1 2 3', '4 5 6'], ['1']),
|
||||||
|
(['4 2', '10 20 10 20', '10 20', '30 40'], ['4']),
|
||||||
|
(['6 2', '1 2 3 4 5 6', '1 3 5', '2 4 6'], ['0']),
|
||||||
|
(['3 3', '1000000000 1000000000 1000000000', '1000000000 999999999 999999998', '1 2 3'], ['3']) # граница: проверка больших чисел (до 10^9)
|
||||||
|
],
|
||||||
|
'minion_game': [
|
||||||
|
(['A'], ['Kevin 1']), # граница: минимальная длина len(S)=1 (гласная)
|
||||||
|
(['B'], ['Stuart 1']), # граница: минимальная длина len(S)=1 (согласная)
|
||||||
|
(['BANANA'], ['Stuart 12']),
|
||||||
|
(['ABC'], ['Draw']),
|
||||||
|
(['AEIOUY'], ['Kevin 21']),
|
||||||
|
(['A' * 1000000], ['Kevin 500000500000']) # граница: максимальная длина len(S)=10^6
|
||||||
|
],
|
||||||
|
'matrix_mult': [
|
||||||
|
(['2', '1 2', '3 4', '5 6', '7 8'], ['19 22', '43 50']), # граница: минимум n=2
|
||||||
|
(['2', '1 0', '0 1', '2 3', '4 5'], ['2 3', '4 5']),
|
||||||
|
(['3', '1 2 3', '4 5 6', '7 8 9', '9 8 7', '6 5 4', '3 2 1'], ['30 24 18', '84 69 54', '138 114 90']),
|
||||||
|
(['10', '1 0 0 0 0 0 0 0 0 0', '0 1 0 0 0 0 0 0 0 0', '0 0 1 0 0 0 0 0 0 0', '0 0 0 1 0 0 0 0 0 0', '0 0 0 0 1 0 0 0 0 0', '0 0 0 0 0 1 0 0 0 0', '0 0 0 0 0 0 1 0 0 0', '0 0 0 0 0 0 0 1 0 0', '0 0 0 0 0 0 0 0 1 0', '0 0 0 0 0 0 0 0 0 1', '1 2 3 4 5 6 7 8 9 10', '10 9 8 7 6 5 4 3 2 1', '1 1 1 1 1 1 1 1 1 1', '2 2 2 2 2 2 2 2 2 2', '3 3 3 3 3 3 3 3 3 3', '4 4 4 4 4 4 4 4 4 4', '5 5 5 5 5 5 5 5 5 5', '6 6 6 6 6 6 6 6 6 6', '7 7 7 7 7 7 7 7 7 7', '8 8 8 8 8 8 8 8 8 8'], ['1 2 3 4 5 6 7 8 9 10', '10 9 8 7 6 5 4 3 2 1', '1 1 1 1 1 1 1 1 1 1', '2 2 2 2 2 2 2 2 2 2', '3 3 3 3 3 3 3 3 3 3', '4 4 4 4 4 4 4 4 4 4', '5 5 5 5 5 5 5 5 5 5', '6 6 6 6 6 6 6 6 6 6', '7 7 7 7 7 7 7 7 7 7', '8 8 8 8 8 8 8 8 8 8']) # граница: максимум n=10
|
||||||
|
],
|
||||||
|
'lists': [
|
||||||
|
(['4', 'append 1', 'append 2', 'insert 1 3', 'print'], ['[1, 3, 2]']),
|
||||||
|
(['3', 'append 5', 'append 10', 'print'], ['[5, 10]']),
|
||||||
|
(['5', 'insert 0 1', 'insert 1 2', 'insert 2 3', 'sort', 'print'], ['[1, 2, 3]'])
|
||||||
|
],
|
||||||
|
'metro': [
|
||||||
|
(['2', '10 30', '20 40', '25'], ['2']),
|
||||||
|
(['3', '5 15', '10 20', '25 35', '12'], ['2']),
|
||||||
|
(['4', '0 10', '5 15', '20 30', '25 35', '10'], ['2'])
|
||||||
|
],
|
||||||
|
'max_word': [
|
||||||
|
# max_word.py читает из файла example.txt и не принимает входные данные
|
||||||
|
],
|
||||||
|
'price_sum': [
|
||||||
|
# price_sum.py читает из файла products.csv и не принимает входные данные
|
||||||
|
# Ожидаемый вывод: "6842.84 5891.06 6810.90"
|
||||||
|
],
|
||||||
|
'pirate_ship': [
|
||||||
|
(['10 3', 'золото 5 100', 'серебро 10 80', 'бронза 15 60'], ['золото 5 100', 'серебро 5 40.0']),
|
||||||
|
(['50 4', 'алмазы 10 500', 'золото 20 400', 'серебро 30 300', 'бронза 40 200'], ['алмазы 10 500', 'золото 20 400', 'серебро 20 200.0']),
|
||||||
|
(['5 2', 'товар1 10 100', 'товар2 3 30'], ['товар1 5 50.0']),
|
||||||
|
(['100 1', 'груз 50 1000'], ['груз 50 1000']), # граница: груз меньше грузоподъемности
|
||||||
|
(['10 1', 'груз 20 1000'], ['груз 10 500.0']) # граница: груз больше грузоподъемности
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Тесты для проверки обработки ошибок (значения вне диапазона)
|
||||||
|
error_test_data = {
|
||||||
|
'python_if_else': [
|
||||||
|
('0', 'Error'), # n < 1
|
||||||
|
('101', 'Error'), # n > 100
|
||||||
|
],
|
||||||
|
'arithmetic_operators': [
|
||||||
|
(['0', '5'], ['Error']), # a < 1
|
||||||
|
(['5', '0'], ['Error']), # b < 1
|
||||||
|
(['10000000001', '5'], ['Error']), # a > 10^10
|
||||||
|
(['5', '10000000001'], ['Error']), # b > 10^10
|
||||||
|
],
|
||||||
|
'loops': [
|
||||||
|
(['0'], ['Error']), # n < 1
|
||||||
|
(['21'], ['Error']), # n > 20
|
||||||
|
],
|
||||||
|
'print_function': [
|
||||||
|
(['0'], ['Error']), # n < 1
|
||||||
|
(['21'], ['Error']), # n > 20
|
||||||
|
],
|
||||||
|
'nested_list': [
|
||||||
|
(['1', 'Alice', '50'], ['Error']), # N < 2
|
||||||
|
(['6', 'A', '1', 'B', '2', 'C', '3', 'D', '4', 'E', '5', 'F', '6'], ['Error']), # N > 5
|
||||||
|
],
|
||||||
|
'swap_case': [
|
||||||
|
(['a' * 1001], ['Error']), # len(s) > 1000
|
||||||
|
],
|
||||||
|
'minion_game': [
|
||||||
|
(['a' * (10 ** 6 + 1)], ['Error']), # len(s) > 10 ** 6
|
||||||
|
],
|
||||||
|
'is_leap': [
|
||||||
|
(['1899'], ['Error']), # year < 1900
|
||||||
|
(['100001'], ['Error']), # year > 10^5
|
||||||
|
],
|
||||||
|
'happiness': [
|
||||||
|
(['0 5', '1', '1', '2'], ['Error']), # n < 1
|
||||||
|
(['5 0', '1 2 3', '1', '2'], ['Error']), # m < 1
|
||||||
|
],
|
||||||
|
'matrix_mult': [
|
||||||
|
(['1'], ['Error']), # n < 2
|
||||||
|
(['11'], ['Error']), # n > 10
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_hello():
|
||||||
|
assert run_script('hello.py') == 'Hello, world!'
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['python_if_else'])
|
||||||
|
def test_python_if_else(input_data, expected):
|
||||||
|
assert run_script('python_if_else.py', [input_data]) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['arithmetic_operators'])
|
||||||
|
def test_arithmetic_operators(input_data, expected):
|
||||||
|
assert run_script('arithmetic_operators.py', input_data).split('\n') == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['division'])
|
||||||
|
def test_division(input_data, expected):
|
||||||
|
assert run_script('division.py', input_data).split('\n') == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['loops'])
|
||||||
|
def test_loops(input_data, expected):
|
||||||
|
assert run_script('loops.py', input_data).split('\n') == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['print_function'])
|
||||||
|
def test_print_function(input_data, expected):
|
||||||
|
assert run_script('print_function.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['second_score'])
|
||||||
|
def test_second_score(input_data, expected):
|
||||||
|
assert run_script('second_score.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['nested_list'])
|
||||||
|
def test_nested_list(input_data, expected):
|
||||||
|
result = run_script('nested_list.py', input_data).split('\n')
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['swap_case'])
|
||||||
|
def test_swap_case(input_data, expected):
|
||||||
|
assert run_script('swap_case.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['split_and_join'])
|
||||||
|
def test_split_and_join(input_data, expected):
|
||||||
|
assert run_script('split_and_join.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['anagram'])
|
||||||
|
def test_anagram(input_data, expected):
|
||||||
|
assert run_script('anagram.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['is_leap'])
|
||||||
|
def test_is_leap(input_data, expected):
|
||||||
|
assert run_script('is_leap.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['happiness'])
|
||||||
|
def test_happiness(input_data, expected):
|
||||||
|
assert run_script('happiness.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['minion_game'])
|
||||||
|
def test_minion_game(input_data, expected):
|
||||||
|
assert run_script('minion_game.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['matrix_mult'])
|
||||||
|
def test_matrix_mult(input_data, expected):
|
||||||
|
assert run_script('matrix_mult.py', input_data).split('\n') == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['lists'])
|
||||||
|
def test_lists(input_data, expected):
|
||||||
|
assert run_script('lists.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['metro'])
|
||||||
|
def test_metro(input_data, expected):
|
||||||
|
assert run_script('metro.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
def test_max_word():
|
||||||
|
# max_word.py читает файл example.txt, поэтому проверяем только что программа запускается
|
||||||
|
result = run_script('max_word.py')
|
||||||
|
assert len(result) > 0
|
||||||
|
words = result.split('\n')
|
||||||
|
assert len(words) > 0
|
||||||
|
|
||||||
|
def test_price_sum():
|
||||||
|
result = run_script('price_sum.py')
|
||||||
|
assert result == '6842.84 5891.06 6810.90'
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['pirate_ship'])
|
||||||
|
def test_pirate_ship(input_data, expected):
|
||||||
|
result = run_script('pirate_ship.py', input_data).split('\n')
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
# Тесты для проверки обработки ошибок (значения вне допустимого диапазона)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['python_if_else'])
|
||||||
|
def test_python_if_else_errors(input_data, expected):
|
||||||
|
assert run_script('python_if_else.py', [input_data]) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['arithmetic_operators'])
|
||||||
|
def test_arithmetic_operators_errors(input_data, expected):
|
||||||
|
result = run_script('arithmetic_operators.py', input_data)
|
||||||
|
assert result == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['loops'])
|
||||||
|
def test_loops_errors(input_data, expected):
|
||||||
|
assert run_script('loops.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['print_function'])
|
||||||
|
def test_print_function_errors(input_data, expected):
|
||||||
|
assert run_script('print_function.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['nested_list'])
|
||||||
|
def test_nested_list_errors(input_data, expected):
|
||||||
|
result = run_script('nested_list.py', input_data)
|
||||||
|
assert result == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['swap_case'])
|
||||||
|
def test_swap_case_errors(input_data, expected):
|
||||||
|
assert run_script('swap_case.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['minion_game'])
|
||||||
|
def test_minion_game_errors(input_data, expected):
|
||||||
|
assert run_script('minion_game.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['is_leap'])
|
||||||
|
def test_is_leap_errors(input_data, expected):
|
||||||
|
assert run_script('is_leap.py', input_data) == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['happiness'])
|
||||||
|
def test_happiness_errors(input_data, expected):
|
||||||
|
result = run_script('happiness.py', input_data)
|
||||||
|
assert result == expected[0]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['matrix_mult'])
|
||||||
|
def test_matrix_mult_errors(input_data, expected):
|
||||||
|
result = run_script('matrix_mult.py', input_data)
|
||||||
|
assert result == expected[0]
|
||||||
354
hws/hw-2/README.md
Normal file
|
|
@ -0,0 +1,354 @@
|
||||||
|
Решите задачи, приведённые ниже. Код каждой задачи разместите в отдельном файле, название которого соответствует указанному в условии задачи. В качестве ответа на это задание загрузите архив (ZIP) с решениями. Архив должен содержать только файлы с решениями, файл test.py и вспомогательные файлы, необходимые для отдельных задач (не должно быть папок).
|
||||||
|
|
||||||
|
Напишите тесты для решённых задач с использованием фреймворка pytest, разместите их в файле test.py. Для каждой задачи достаточно рассмотреть принципиально различные ситуации (входные данные). Всего должно быть написано не менее 60 тестов. В отличие от предыдущего домашнего задания, в данном задании предполагается, что решение каждой задачи будет представлено в виде функции (если не сказано иного), которую вам необходимо импортировать в файле test.py для последующего тестирования (т. е. в тестах должны непосредственно вызываться тестируемые функции). При импорте функций не должно выполняться ничего лишнего. При необходимости выполнить какие-либо действия при запуске файла с программой (например, запросить пользовательский ввод, осуществить замер времени выполнения функции т. д.) не забудьте заключить их в условие `if __name__ == '__main__':`.
|
||||||
|
|
||||||
|
При обнаружении ошибок или неточностей в формулировках задач свяжитесь с преподавателем.
|
||||||
|
|
||||||
|
## 1) Вычисление факториала
|
||||||
|
|
||||||
|
Реализуйте рекурсивную (fact_rec) и итерационную (fact_it) функции вычисления факториала целого положительного числа n. Сравните скорость работы реализованных функций, результат сравнения приведите в комментарии в файле с программой. Для измерения времени выполнения можно использовать модули `time` или `timeit`. Функцией math.factorial пользоваться нельзя.
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
|
||||||
|
- 1 ⩽ n ⩽ 10⁵
|
||||||
|
|
||||||
|
**Название файла:** fact.py
|
||||||
|
|
||||||
|
## 2) Данные сотрудника
|
||||||
|
|
||||||
|
Реализуйте функцию show_employee, принимающую в качестве аргументов имя сотрудника (name) и величину его заработной платы (salary) и возвращающую строку с данными в следующем формате: "Иванов Иван Иванович: 30000 ₽". В случае, если значение заработной платы не было передано при вызове функции, установить его равным 100000.
|
||||||
|
|
||||||
|
**Название файла:** show_employee.py
|
||||||
|
|
||||||
|
## 3) Сумма и разность
|
||||||
|
|
||||||
|
Реализуйте функцию sum_and_sub, которая принимает в качестве аргументов два действительных числа (a и b) и возвращает их сумму и разность.
|
||||||
|
|
||||||
|
**Название файла:** sum_and_sub.py
|
||||||
|
|
||||||
|
## 4) Обработка списка
|
||||||
|
|
||||||
|
В файле process_list.py приведён код функции обработки данных списка (arr).
|
||||||
|
|
||||||
|
Перепишите приведённую функцию с использованием list comprehension. Реализуйте функцию-генератор с аналогичной функциональностью (process_list_gen). Сравните скорость работы реализованных функций, результат сравнения приведите в комментарии в файле с программой.
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
|
||||||
|
- 1 ⩽ len(arr) ⩽ 10³
|
||||||
|
|
||||||
|
**Название файла:** process_list.py
|
||||||
|
|
||||||
|
## 5) Функция сложения с произвольным количеством аргументов
|
||||||
|
|
||||||
|
Реализуйте функцию my_sum, принимающую произвольное количество аргументов (действительных чисел) и возвращающую их сумму.
|
||||||
|
|
||||||
|
**Название файла:** my_sum.py
|
||||||
|
|
||||||
|
## 6) Аргументы командной строки
|
||||||
|
|
||||||
|
Перепишите программу из 5-й задачи таким образом, чтобы числа для сложения передавались при вызове программы как аргументы командной строки, а результат выводился на стандартный поток вывода.
|
||||||
|
|
||||||
|
**Название файла:** my_sum_argv.py
|
||||||
|
|
||||||
|
**Пример вызова:** python3 my_sum_argv.py 1 2 3 4 5
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
15
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7) Сортировка файлов
|
||||||
|
|
||||||
|
Напишите программу, которая в качестве аргумента командной строки принимает путь до некоторой директории и выводит список всех файлов в этой директории, упорядоченных в лексикографическом порядке и сгруппированных по расширению. Имена вложенных подкаталогов выводить не нужно. Например, если в директории находятся файлы a.py, a.txt, b.py, b.txt, c.py, c.txt, они должны быть выведены в следующем порядке:
|
||||||
|
|
||||||
|
```
|
||||||
|
a.py
|
||||||
|
b.py
|
||||||
|
c.py
|
||||||
|
a.txt
|
||||||
|
b.txt
|
||||||
|
c.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Подсказка:** для того, чтобы отличить строку с именем файла от строки с именем подкаталога можно использовать функции os.path.isfile или os.path.isdir.
|
||||||
|
|
||||||
|
**Название файла:** files_sort.py
|
||||||
|
|
||||||
|
## 8) Рекурсивный поиск файла
|
||||||
|
|
||||||
|
Напишите программу, которая в качестве аргумента командной строки принимает имя файла и осуществляет рекурсивный поиск указанного файла относительно директории, в которой находится файл с программой. Если файл найден -- вывести первые 5 строк, в противном случае -- вывести сообщение "Файл {название_файла} не найден".
|
||||||
|
|
||||||
|
**Подсказка:** если вы пишете рекурсивную функцию, то после рекурсивного вызова нужно возвращаться в родительский каталог при помощи os.chdir(".."). Другой вариант решения задачи: воспользоваться функцией os.walk.
|
||||||
|
|
||||||
|
**Название файла:** file_search.py
|
||||||
|
|
||||||
|
## 9) Валидация адресов электронной почты
|
||||||
|
|
||||||
|
Вам дается целое число N, за которым следуют N адресов электронной почты. Ваша задача -- вывести список, содержащий только корректные адреса электронной почты в лексикографическом порядке.
|
||||||
|
|
||||||
|
Корректные адреса электронной почты должны соответствовать следующим правилам:
|
||||||
|
|
||||||
|
- Он должен иметь формат username@websitename.extension.
|
||||||
|
- Имя пользователя может содержать только цифры, латинские буквы, тире и подчеркивания.
|
||||||
|
- Название веб-сайта может содержать только цифры и латинские буквы.
|
||||||
|
- Расширение может содержать только латинские буквы.
|
||||||
|
- Максимальная длина расширения составляет 3.
|
||||||
|
|
||||||
|
Допишите функцию fun, которая прилагается к заданию, чтобы она возвращала True для корректных адресов, а для остальных -- False.
|
||||||
|
|
||||||
|
**Формат ввода:**
|
||||||
|
|
||||||
|
Первая строка ввода -- целое число, количество адресов электронной почты. Далее следуют N строк, каждая из которых содержит адрес электронной почты.
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
|
||||||
|
Не может быть введено пустых значений.
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
3
|
||||||
|
lara@mospolytech.ru
|
||||||
|
brian-23@mospolytech.ru
|
||||||
|
britts_54@mospolytech.ru
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
['brian-23@mospolytech.ru', 'britts_54@mospolytech.ru', 'lara@mospolytech.ru']
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** email_validation.py
|
||||||
|
|
||||||
|
## 10) Числа Фибоначчи
|
||||||
|
|
||||||
|
Дополните код, приведённый в файле fibonacci.py. Вы должны сгенерировать список первых n чисел Фибоначчи, начиная с 0. Затем примените функцию map и лямбда-выражение для возведения в куб каждого из полученных чисел Фибоначчи и выведите список на стандартный поток вывода.
|
||||||
|
|
||||||
|
**Формат ввода:**
|
||||||
|
|
||||||
|
Одна строка: целое число n.
|
||||||
|
|
||||||
|
**Формат вывода:**
|
||||||
|
|
||||||
|
Список в одной строке, содержащий кубы первых n чисел Фибоначчи.
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
|
||||||
|
- 1 ⩽ n ⩽ 15
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
[0, 1, 1, 8, 27]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** fibonacci.py
|
||||||
|
|
||||||
|
## 11) Средние оценки
|
||||||
|
|
||||||
|
Университет проводит экзамен для N студентов по X предметам. Реализуйте функцию compute_average_scores, которая подсчитывает средние баллы каждого студента. Функция принимает один параметр scores -- список из X кортежей, каждый из которых содержит по N чисел в диапазоне от 0 до 100. Каждый кортеж соответствует оценкам по определённому предмету. Элементы с одинаковым индексом в каждом из кортежей соответствуют одному и тому же студенту. Возвращает функция кортеж из X элементов -- средние баллы каждого студента.
|
||||||
|
|
||||||
|
**Формат ввода:**
|
||||||
|
|
||||||
|
Первая строка содержит значения N и X, разделенные пробелом. Следующие X строк содержат оценки, разделенные пробелом, полученные учащимися по определенному предмету.
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
|
||||||
|
- 0 < N ⩽ 100
|
||||||
|
- 0 < X ⩽ 100
|
||||||
|
|
||||||
|
**Формат вывода:**
|
||||||
|
|
||||||
|
Выведите средние значения по всем учащимся в отдельных строках. Средние значения должны быть корректными с точностью до 1 десятичного знака.
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
5 3
|
||||||
|
89 90 78 93 80
|
||||||
|
90 91 85 88 86
|
||||||
|
91 92 83 89 90.5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
90.0
|
||||||
|
91.0
|
||||||
|
82.0
|
||||||
|
90.0
|
||||||
|
85.5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** average_scores.py
|
||||||
|
|
||||||
|
## 12) Угол между плоскостями
|
||||||
|
|
||||||
|
Вам даны четыре точки (A, B, C, D) в трехмерной декартовой системе координат. Вам нужно вычислить угол между плоскостями, образованными точками A, B, C и B, C, D в градусах (не в радианах). Обозначим этот угол как ϕ, тогда cos(ϕ) = (X, Y)/|X||Y|, где X = AB × BC и Y = BC × CD. (X, Y) -- скалярное произведение X и Y, AB × BC -- векторное произведение AB и BC, где AB = B − A и BC = B − C. Оформите решение в виде функции plane_angle, которая принимает четыре аргумента: точки A, B, C, D (экземпляры класса Point). В файле plane_angle.py приведена заготовка класса Point -- допишите недостающие методы, которые вам потребуются для решения задачи. Функция plane_angle должна возвращать одно число -- искомый угол в градусах.
|
||||||
|
|
||||||
|
**Название файла:** plane_angle.py
|
||||||
|
|
||||||
|
## 13) Форматирование номера телефона
|
||||||
|
|
||||||
|
Вам даны номера мобильных телефонов. Отсортируйте их в порядке возрастания, затем выведите в стандартном формате: +7 (xxx) xxx-xx-xx. Указанные мобильные номера могут начинаться с 8, +7 или 0, после чего следует десятизначный номер. Также префикс может отсутствовать вовсе. Вам дана заготовка кода в файле phone_number.py -- заполните недостающие фрагменты.
|
||||||
|
|
||||||
|
**Формат ввода:**
|
||||||
|
|
||||||
|
Первая строка ввода содержит целое число N, количество номеров мобильных телефонов. Далее следуют N строк, содержащие номера мобильных телефонов.
|
||||||
|
|
||||||
|
**Формат вывода:**
|
||||||
|
|
||||||
|
Выведите N номеров мобильных телефонов отдельными строками в требуемом формате.
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
3
|
||||||
|
07895462130
|
||||||
|
89875641230
|
||||||
|
9195969878
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
+7 (789) 546-21-30
|
||||||
|
+7 (919) 596-98-78
|
||||||
|
+7 (987) 564-12-30
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** phone_number.py
|
||||||
|
|
||||||
|
## 14) Сортировка списка
|
||||||
|
|
||||||
|
Вам предоставляется некоторая информация о людях. Для каждого человека в вашем распоряжении есть имя, фамилия, возраст и пол. Выведите их имена в определенном формате (см. ниже), отсортируйте список по возрасту в порядке возрастания, т.е. имя самого младшего человека должно быть напечатано первым. Для двух человек одного возраста выведите их в порядке ввода. Вам дана заготовка кода в файле people_sort.py -- заполните недостающие фрагменты.
|
||||||
|
|
||||||
|
**Формат ввода:**
|
||||||
|
|
||||||
|
Первая строка содержит целое число N, количество человек. Следующие N строк содержат разделенные пробелом значения имени, фамилии, возраста и пола соответственно.
|
||||||
|
|
||||||
|
**Формат вывода:**
|
||||||
|
|
||||||
|
Выведите имена в отдельных строках в приведённом ниже формате в порядке возрастания возраста.
|
||||||
|
|
||||||
|
Пример формата вывода: для "Henry Davids" вывод должен быть "Mr. Henry Davids", для "Mary George" вывод должен быть "Ms. Mary George".
|
||||||
|
|
||||||
|
**Ограничения:**
|
||||||
|
|
||||||
|
- 1 ⩽ N ⩽ 10
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
3
|
||||||
|
Mike Thomson 20 M
|
||||||
|
Robert Bustle 32 M
|
||||||
|
Andria Bustle 30 F
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Mr. Mike Thomson
|
||||||
|
Ms. Andria Bustle
|
||||||
|
Mr. Robert Bustle
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** people_sort.py
|
||||||
|
|
||||||
|
## 15) Комплексные числа
|
||||||
|
|
||||||
|
Для этого задания вам даны два комплексных числа, и вы должны вывести результат их сложения, вычитания, умножения, деления и операции вычисления модуля. Действительная и мнимая части должны быть представлены с точностью до двух знаков после запятой. Вам дана заготовка кода в файле complex_numbers.py -- заполните недостающие фрагменты.
|
||||||
|
|
||||||
|
**Формат ввода:**
|
||||||
|
|
||||||
|
Одна строка ввода: действительная и мнимая части числа, разделенные пробелом.
|
||||||
|
|
||||||
|
**Формат вывода:**
|
||||||
|
|
||||||
|
Для двух комплексных чисел C и D выходные данные должны быть в следующей последовательности в отдельных строках:
|
||||||
|
|
||||||
|
- C + D,
|
||||||
|
- C - D,
|
||||||
|
- C * D,
|
||||||
|
- C/D,
|
||||||
|
- mod(C),
|
||||||
|
- mod(D).
|
||||||
|
|
||||||
|
Для комплексных чисел с ненулевой действительной (A) и комплексной (B) частью выходные данные должны быть в следующем формате: A + Bi. Знак "+" должен быть заменён на "-", если B < 0.
|
||||||
|
|
||||||
|
Для комплексных чисел с нулевой комплексной частью (B), т.е. действительных чисел, выходные данные должны быть в формате: A + 0.00i.
|
||||||
|
|
||||||
|
Для комплексных чисел, где действительная часть (A) равна нулю, а комплексная часть (B) ненулевая, выходные данные должны быть: 0.00 + Bi.
|
||||||
|
|
||||||
|
**Пример ввода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
2 1
|
||||||
|
5 6
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример вывода:**
|
||||||
|
|
||||||
|
```
|
||||||
|
7.00+7.00i
|
||||||
|
-3.00-5.00i
|
||||||
|
4.00+17.00i
|
||||||
|
0.26-0.11i
|
||||||
|
2.24+0.00i
|
||||||
|
7.81+0.00i
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** complex_numbers.py
|
||||||
|
|
||||||
|
## 16) Монте-Карло
|
||||||
|
|
||||||
|
Реализуйте вычисление площади окружности методом Монте-Карло. Оформите решение в виде функции circle_square_mk, которая принимает два параметра: r -- радиус окружности и n -- количество экспериментов в методе Монте-Карло, и возвращает действительное число -- полученную площадь. При тестировании функции сравните полученные результаты с площадью, вычисленной по формуле. Оцените получаемую погрешность расчёта в зависимости от количества экспериментов (n), результат укажите в комментарии.
|
||||||
|
|
||||||
|
**Название файла:** circle_square_mk.py
|
||||||
|
|
||||||
|
## 17) Декоратор для логирования
|
||||||
|
|
||||||
|
Напишите декоратор function_logger, который принимает в качестве аргумента путь к файлу. Если данный декоратор добавить к функции, то в указанный файл будет логироваться информация вида:
|
||||||
|
|
||||||
|
- Название функции,
|
||||||
|
- Время вызова функции (в формате yyyy-mm-dd HH:MM:SS),
|
||||||
|
- Входящие аргументы (при наличии: в виде кортежа -- позиционные, в виде словаря -- ключевые),
|
||||||
|
- Возвращаемое значение (если есть, если нет то логировать '-')
|
||||||
|
- Время завершения работы функции (в формате yyyy-mm-dd HH:MM:SS)
|
||||||
|
- Время работы функции (в секундах).
|
||||||
|
|
||||||
|
При отсутствии указанного файла он должен быть создан, в противном случае логи должны добавляться в конец файла.
|
||||||
|
|
||||||
|
**Пример использования:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
@function_logger('test.log')
|
||||||
|
def greeting_format(name):
|
||||||
|
return f'Hello, {name}!'
|
||||||
|
|
||||||
|
greeting_format('John')
|
||||||
|
```
|
||||||
|
|
||||||
|
**Пример содержимого файла test.log:**
|
||||||
|
|
||||||
|
```
|
||||||
|
greeting_format
|
||||||
|
2024-02-19 16:00:12.670738
|
||||||
|
('John',)
|
||||||
|
Hello, John!
|
||||||
|
2024-02-19 16:00:12.670741
|
||||||
|
0:00:00.000003
|
||||||
|
```
|
||||||
|
|
||||||
|
**Название файла:** log_decorator.py
|
||||||
|
|
||||||
|
**Замечание:** При написании тестов вам может быть полезна библиотека FreezeGun.
|
||||||
22
hws/hw-2/average_scores.py
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
def compute_average_scores(scores):
|
||||||
|
if not scores or len(scores) <= 0 or len(scores) > 100:
|
||||||
|
return "Error"
|
||||||
|
num_students = len(scores[0])
|
||||||
|
if num_students <= 0 or num_students > 100:
|
||||||
|
return "Error"
|
||||||
|
averages = []
|
||||||
|
for i in range(num_students):
|
||||||
|
total = sum(score[i] for score in scores)
|
||||||
|
avg = total / len(scores)
|
||||||
|
averages.append(avg)
|
||||||
|
return tuple(averages)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
n, x = map(int, input().split())
|
||||||
|
scores = []
|
||||||
|
for _ in range(x):
|
||||||
|
scores.append(tuple(map(float, input().split())))
|
||||||
|
|
||||||
|
result = compute_average_scores(scores)
|
||||||
|
for avg in result:
|
||||||
|
print(f"{avg:.1f}")
|
||||||
13
hws/hw-2/circle_square_mk.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import random
|
||||||
|
import math
|
||||||
|
|
||||||
|
def circle_square_mk(r, n):
|
||||||
|
inside = 0
|
||||||
|
for _ in range(n):
|
||||||
|
x = random.uniform(-r, r)
|
||||||
|
y = random.uniform(-r, r)
|
||||||
|
if x**2 + y**2 <= r**2:
|
||||||
|
inside += 1
|
||||||
|
square_area = (2 * r) ** 2
|
||||||
|
circle_area = square_area * inside / n
|
||||||
|
return circle_area
|
||||||
47
hws/hw-2/complex_numbers.py
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import math
|
||||||
|
|
||||||
|
class Complex(object):
|
||||||
|
def __init__(self, real, imaginary):
|
||||||
|
self.real = real
|
||||||
|
self.imaginary = imaginary
|
||||||
|
|
||||||
|
def __add__(self, no):
|
||||||
|
return Complex(self.real + no.real, self.imaginary + no.imaginary)
|
||||||
|
|
||||||
|
def __sub__(self, no):
|
||||||
|
return Complex(self.real - no.real, self.imaginary - no.imaginary)
|
||||||
|
|
||||||
|
def __mul__(self, no):
|
||||||
|
real = self.real * no.real - self.imaginary * no.imaginary
|
||||||
|
imaginary = self.real * no.imaginary + self.imaginary * no.real
|
||||||
|
return Complex(real, imaginary)
|
||||||
|
|
||||||
|
def __truediv__(self, no):
|
||||||
|
denominator = no.real ** 2 + no.imaginary ** 2
|
||||||
|
real = (self.real * no.real + self.imaginary * no.imaginary) / denominator
|
||||||
|
imaginary = (self.imaginary * no.real - self.real * no.imaginary) / denominator
|
||||||
|
return Complex(real, imaginary)
|
||||||
|
|
||||||
|
def mod(self):
|
||||||
|
return Complex(math.sqrt(self.real ** 2 + self.imaginary ** 2), 0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.imaginary == 0:
|
||||||
|
result = "%.2f+0.00i" % (self.real)
|
||||||
|
elif self.real == 0:
|
||||||
|
if self.imaginary >= 0:
|
||||||
|
result = "0.00+%.2fi" % (self.imaginary)
|
||||||
|
else:
|
||||||
|
result = "0.00-%.2fi" % (abs(self.imaginary))
|
||||||
|
elif self.imaginary > 0:
|
||||||
|
result = "%.2f+%.2fi" % (self.real, self.imaginary)
|
||||||
|
else:
|
||||||
|
result = "%.2f-%.2fi" % (self.real, abs(self.imaginary))
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
c = map(float, input().split())
|
||||||
|
d = map(float, input().split())
|
||||||
|
x = Complex(*c)
|
||||||
|
y = Complex(*d)
|
||||||
|
print(*map(str, [x+y, x-y, x*y, x/y, x.mod(), y.mod()]), sep='\n')
|
||||||
17
hws/hw-2/email_validation.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
def fun(s):
|
||||||
|
import re
|
||||||
|
pattern = r'^[a-zA-Z0-9_-]+@[a-zA-Z0-9]+\.[a-zA-Z]{1,3}$'
|
||||||
|
return bool(re.match(pattern, s))
|
||||||
|
|
||||||
|
def filter_mail(emails):
|
||||||
|
return list(filter(fun, emails))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
n = int(input())
|
||||||
|
emails = []
|
||||||
|
for _ in range(n):
|
||||||
|
emails.append(input())
|
||||||
|
|
||||||
|
filtered_emails = filter_mail(emails)
|
||||||
|
filtered_emails.sort()
|
||||||
|
print(filtered_emails)
|
||||||
36
hws/hw-2/fact.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import time
|
||||||
|
|
||||||
|
def fact_rec(n):
|
||||||
|
if n < 1 or n >= 1000:
|
||||||
|
return "Error"
|
||||||
|
if n <= 1:
|
||||||
|
return 1
|
||||||
|
return n * fact_rec(n - 1)
|
||||||
|
|
||||||
|
def fact_it(n):
|
||||||
|
if n < 1 or n >= 1000:
|
||||||
|
return "Error"
|
||||||
|
result = 1
|
||||||
|
for i in range(2, n + 1):
|
||||||
|
result *= i
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
n = 999
|
||||||
|
|
||||||
|
max_it = 0
|
||||||
|
for _ in range(1000):
|
||||||
|
start_1 = time.time()
|
||||||
|
fact_it(n)
|
||||||
|
it_time = time.time() - start_1
|
||||||
|
if max_it < it_time: max_it = it_time
|
||||||
|
|
||||||
|
max_rec = 0
|
||||||
|
for _ in range(1000):
|
||||||
|
start_2 = time.time()
|
||||||
|
fact_rec(n)
|
||||||
|
rec_time = time.time() - start_2
|
||||||
|
if max_rec < rec_time: max_rec = rec_time
|
||||||
|
|
||||||
|
print(f"Итеративная: {max_it}с") # 0.001142740249633789
|
||||||
|
print(f"Рекурсивная: {max_rec}с") # 0.0011646747589111328
|
||||||
15
hws/hw-2/fibonacci.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
cube = lambda x: x**3
|
||||||
|
|
||||||
|
def fibonacci(n):
|
||||||
|
if n < 1 or n > 15:
|
||||||
|
return "Error"
|
||||||
|
if n == 1:
|
||||||
|
return [0]
|
||||||
|
result = [0, 1]
|
||||||
|
for i in range(2, n):
|
||||||
|
result.append(result[-1] + result[-2])
|
||||||
|
return result
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
n = int(input())
|
||||||
|
print(list(map(cube, fibonacci(n))))
|
||||||
30
hws/hw-2/file_search.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
filename = sys.argv[1]
|
||||||
|
found = False
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk('.'):
|
||||||
|
if filename in files:
|
||||||
|
file_path = os.path.join(root, filename)
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
for i, line in enumerate(lines[:5]):
|
||||||
|
print(line, end='')
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
for i, line in enumerate(lines[:5]):
|
||||||
|
print(line, end='')
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print(f"Файл {filename} не найден")
|
||||||
25
hws/hw-2/files_sort.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
directory = sys.argv[1]
|
||||||
|
|
||||||
|
files = []
|
||||||
|
for item in os.listdir(directory):
|
||||||
|
full_path = os.path.join(directory, item)
|
||||||
|
if os.path.isfile(full_path):
|
||||||
|
files.append(item)
|
||||||
|
|
||||||
|
files_by_ext = {}
|
||||||
|
for file in files:
|
||||||
|
if '.' in file:
|
||||||
|
ext = file.split('.')[-1]
|
||||||
|
else:
|
||||||
|
ext = ''
|
||||||
|
if ext not in files_by_ext:
|
||||||
|
files_by_ext[ext] = []
|
||||||
|
files_by_ext[ext].append(file)
|
||||||
|
|
||||||
|
for ext in sorted(files_by_ext.keys()):
|
||||||
|
for file in sorted(files_by_ext[ext]):
|
||||||
|
print(file)
|
||||||
31
hws/hw-2/log_decorator.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
def function_logger(log_file):
|
||||||
|
def decorator(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
start_time = datetime.now()
|
||||||
|
start_timestamp = time.time()
|
||||||
|
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
|
||||||
|
end_time = datetime.now()
|
||||||
|
duration = time.time() - start_timestamp
|
||||||
|
|
||||||
|
with open(log_file, 'a', encoding='utf-8') as f:
|
||||||
|
f.write(func.__name__ + '\n')
|
||||||
|
f.write(str(start_time) + '\n')
|
||||||
|
if args:
|
||||||
|
f.write(str(args) + '\n')
|
||||||
|
if kwargs:
|
||||||
|
f.write(str(kwargs) + '\n')
|
||||||
|
if result is not None:
|
||||||
|
f.write(str(result) + '\n')
|
||||||
|
else:
|
||||||
|
f.write('-\n')
|
||||||
|
f.write(str(end_time) + '\n')
|
||||||
|
f.write(f"{duration:.6f}\n")
|
||||||
|
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
2
hws/hw-2/my_sum.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
def my_sum(*args):
|
||||||
|
return sum(args)
|
||||||
8
hws/hw-2/my_sum_argv.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def my_sum(*args):
|
||||||
|
return sum(args)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
numbers = [float(x) for x in sys.argv[1:]]
|
||||||
|
print(int(my_sum(*numbers)))
|
||||||
15
hws/hw-2/people_sort.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import operator
|
||||||
|
|
||||||
|
def person_lister(f):
|
||||||
|
def inner(people):
|
||||||
|
people.sort(key=lambda x: int(x[2]))
|
||||||
|
return [f(person) for person in people]
|
||||||
|
return inner
|
||||||
|
|
||||||
|
@person_lister
|
||||||
|
def name_format(person):
|
||||||
|
return ("Mr. " if person[3] == "M" else "Ms. ") + person[0] + " " + person[1]
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
people = [input().split() for i in range(int(input()))]
|
||||||
|
print(*name_format(people), sep='\n')
|
||||||
21
hws/hw-2/phone_number.py
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
def wrapper(f):
|
||||||
|
def fun(l):
|
||||||
|
normalized = []
|
||||||
|
for phone in l:
|
||||||
|
digits = ''.join(filter(str.isdigit, phone))
|
||||||
|
if len(digits) == 11:
|
||||||
|
digits = digits[1:]
|
||||||
|
elif len(digits) > 10:
|
||||||
|
digits = digits[-10:]
|
||||||
|
normalized.append(digits)
|
||||||
|
formatted = [f"+7 ({num[:3]}) {num[3:6]}-{num[6:8]}-{num[8:]}" for num in normalized]
|
||||||
|
return f(formatted)
|
||||||
|
return fun
|
||||||
|
|
||||||
|
@wrapper
|
||||||
|
def sort_phone(l):
|
||||||
|
return sorted(l)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
l = [input() for _ in range(int(input()))]
|
||||||
|
print(*sort_phone(l), sep='\n')
|
||||||
38
hws/hw-2/plane_angle.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import math
|
||||||
|
|
||||||
|
class Point:
|
||||||
|
def __init__(self, x, y, z):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
self.z = z
|
||||||
|
|
||||||
|
def __sub__(self, no):
|
||||||
|
return Point(self.x - no.x, self.y - no.y, self.z - no.z)
|
||||||
|
|
||||||
|
def dot(self, no):
|
||||||
|
return self.x * no.x + self.y * no.y + self.z * no.z
|
||||||
|
|
||||||
|
def cross(self, no):
|
||||||
|
return Point(
|
||||||
|
self.y * no.z - self.z * no.y,
|
||||||
|
self.z * no.x - self.x * no.z,
|
||||||
|
self.x * no.y - self.y * no.x
|
||||||
|
)
|
||||||
|
|
||||||
|
def absolute(self):
|
||||||
|
return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2)
|
||||||
|
|
||||||
|
def plane_angle(a, b, c, d):
|
||||||
|
ab = b - a
|
||||||
|
bc = c - b
|
||||||
|
cd = d - c
|
||||||
|
|
||||||
|
x = ab.cross(bc)
|
||||||
|
y = bc.cross(cd)
|
||||||
|
|
||||||
|
cos_phi = x.dot(y) / (x.absolute() * y.absolute())
|
||||||
|
phi = math.acos(cos_phi)
|
||||||
|
return math.degrees(phi)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
pass
|
||||||
46
hws/hw-2/process_list.py
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
def process_list(arr):
|
||||||
|
if len(arr) < 1 or len(arr) > 1000:
|
||||||
|
return "Error"
|
||||||
|
result = []
|
||||||
|
for i in arr:
|
||||||
|
if i % 2 == 0:
|
||||||
|
result.append(i**2)
|
||||||
|
else:
|
||||||
|
result.append(i**3)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def process_list_lc(arr):
|
||||||
|
if len(arr) < 1 or len(arr) > 1000:
|
||||||
|
return "Error"
|
||||||
|
return [i**2 if i % 2 == 0 else i**3 for i in arr]
|
||||||
|
|
||||||
|
def process_list_gen(arr):
|
||||||
|
if len(arr) < 1 or len(arr) > 1000:
|
||||||
|
yield "Error"
|
||||||
|
return
|
||||||
|
for i in arr:
|
||||||
|
yield i**2 if i % 2 == 0 else i**3
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import time
|
||||||
|
|
||||||
|
test_arr = list(range(1, 1000))
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
for _ in range(10):
|
||||||
|
process_list(test_arr)
|
||||||
|
orig_time = time.time() - start
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
for _ in range(10):
|
||||||
|
process_list_lc(test_arr)
|
||||||
|
lc_time = time.time() - start
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
for _ in range(10):
|
||||||
|
list(process_list_gen(test_arr))
|
||||||
|
gen_time = time.time() - start
|
||||||
|
|
||||||
|
print(f"Исходная: {orig_time}с") # 0.0019960403442382812
|
||||||
|
print(f"List comprehension: {lc_time}с") # 0.001004934310913086
|
||||||
|
print(f"Генератор: {gen_time}с") # 0.0009999275207519531
|
||||||
3
hws/hw-2/show_employee.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
def show_employee(name, salary=100000):
|
||||||
|
return f"{name}: {salary} ₽"
|
||||||
|
|
||||||
2
hws/hw-2/sum_and_sub.py
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
def sum_and_sub(a, b):
|
||||||
|
return a + b, a - b
|
||||||
523
hws/hw-2/test.py
Normal file
|
|
@ -0,0 +1,523 @@
|
||||||
|
import pytest
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from fact import fact_it, fact_rec
|
||||||
|
from show_employee import show_employee
|
||||||
|
from sum_and_sub import sum_and_sub
|
||||||
|
from process_list import process_list, process_list_lc, process_list_gen
|
||||||
|
from my_sum import my_sum
|
||||||
|
from email_validation import fun
|
||||||
|
from fibonacci import fibonacci
|
||||||
|
from average_scores import compute_average_scores
|
||||||
|
from plane_angle import Point, plane_angle
|
||||||
|
from complex_numbers import Complex
|
||||||
|
from circle_square_mk import circle_square_mk
|
||||||
|
from log_decorator import function_logger
|
||||||
|
from people_sort import name_format
|
||||||
|
from phone_number import sort_phone
|
||||||
|
|
||||||
|
# ==================== Тестовые данные ====================
|
||||||
|
|
||||||
|
test_data = {
|
||||||
|
'fact_it': [
|
||||||
|
(1, 1), # граница: минимум n=1
|
||||||
|
(2, 2),
|
||||||
|
(5, 120),
|
||||||
|
(7, 5040),
|
||||||
|
(10, 3628800),
|
||||||
|
],
|
||||||
|
'fact_rec': [
|
||||||
|
(1, 1), # граница: минимум n=1
|
||||||
|
(2, 2),
|
||||||
|
(5, 120),
|
||||||
|
(7, 5040),
|
||||||
|
(10, 3628800),
|
||||||
|
],
|
||||||
|
'show_employee': [
|
||||||
|
(("Иванов Иван Иванович", 30000), "Иванов Иван Иванович: 30000 ₽"),
|
||||||
|
(("Петров Петр Петрович",), "Петров Петр Петрович: 100000 ₽"),
|
||||||
|
(("Сидоров Сидор Сидорович", 0), "Сидоров Сидор Сидорович: 0 ₽"),
|
||||||
|
(("Test User", 1000000), "Test User: 1000000 ₽"),
|
||||||
|
(("", 50000), ": 50000 ₽"),
|
||||||
|
(("Test", -1000), "Test: -1000 ₽"),
|
||||||
|
],
|
||||||
|
'sum_and_sub': [
|
||||||
|
((5, 3), (8, 2)),
|
||||||
|
((-5, -3), (-8, -2)),
|
||||||
|
((5, -3), (2, 8)),
|
||||||
|
((5.5, 2.5), (8.0, 3.0)),
|
||||||
|
((0, 0), (0, 0)),
|
||||||
|
((1000.5, 500.5), (1501.0, 500.0)),
|
||||||
|
((-10, -5), (-15, -5)),
|
||||||
|
],
|
||||||
|
'process_list': [
|
||||||
|
([1, 2, 3, 4], [1, 4, 27, 16]),
|
||||||
|
([2, 4, 6], [4, 16, 36]),
|
||||||
|
([1, 3, 5], [1, 27, 125]),
|
||||||
|
([5], [125]), # граница: минимум len(arr)=1
|
||||||
|
],
|
||||||
|
'my_sum': [
|
||||||
|
((1, 2), 3),
|
||||||
|
((1, 2, 3, 4, 5), 15),
|
||||||
|
((), 0),
|
||||||
|
((1.5, 2.5, 3.0), 7.0),
|
||||||
|
((-1, -2, -3), -6),
|
||||||
|
((5,), 5),
|
||||||
|
((1000, 2000, 3000), 6000),
|
||||||
|
((10, -5, 3, -2), 6),
|
||||||
|
],
|
||||||
|
'email_validation': [
|
||||||
|
("test@example.com", True),
|
||||||
|
("test_user@example.com", True),
|
||||||
|
("test-user@example.com", True),
|
||||||
|
("testexample.com", False),
|
||||||
|
("test@exam!ple.com", False),
|
||||||
|
("test@example.comm", False), # extension > 3
|
||||||
|
("user123@test45.com", True),
|
||||||
|
("user123@test.com", True),
|
||||||
|
("test@site123.com", True),
|
||||||
|
("test@example", False),
|
||||||
|
("@example.com", False),
|
||||||
|
("test@.com", False),
|
||||||
|
("test@example.abc", True), # граница: максимум extension=3
|
||||||
|
],
|
||||||
|
'fibonacci': [
|
||||||
|
(1, [0]), # граница: минимум n=1
|
||||||
|
(2, [0, 1]),
|
||||||
|
(5, [0, 1, 1, 2, 3]),
|
||||||
|
(8, [0, 1, 1, 2, 3, 5, 8, 13]),
|
||||||
|
(10, [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]),
|
||||||
|
],
|
||||||
|
'complex_add': [
|
||||||
|
((2, 1, 5, 6), (7, 7)),
|
||||||
|
((100, 200, 50, 75), (150, 275)),
|
||||||
|
],
|
||||||
|
'complex_sub': [
|
||||||
|
((2, 1, 5, 6), (-3, -5)),
|
||||||
|
],
|
||||||
|
'complex_mul': [
|
||||||
|
((2, 1, 5, 6), (4, 17)),
|
||||||
|
],
|
||||||
|
'complex_str': [
|
||||||
|
((2, 1), "2.00+1.00i"),
|
||||||
|
((2, -1), "2.00-1.00i"),
|
||||||
|
((2, 0), "2.00+0.00i"),
|
||||||
|
((0, 5), "0.00+5.00i"),
|
||||||
|
((0, 0), "0.00+0.00i"),
|
||||||
|
],
|
||||||
|
'point_sub': [
|
||||||
|
((5, 3, 2, 1, 1, 1), (4, 2, 1)),
|
||||||
|
((-1, -2, -3, -4, -5, -6), (3, 3, 3)),
|
||||||
|
],
|
||||||
|
'people_sort': [
|
||||||
|
([["Mike", "Thomson", "20", "M"],
|
||||||
|
["Robert", "Downey", "30", "M"],
|
||||||
|
["Alice", "Wonder", "25", "F"]],
|
||||||
|
["Mr. Mike Thomson", "Ms. Alice Wonder", "Mr. Robert Downey"]),
|
||||||
|
([["Anna", "Smith", "18", "F"],
|
||||||
|
["John", "Doe", "18", "M"]],
|
||||||
|
["Ms. Anna Smith", "Mr. John Doe"]),
|
||||||
|
([["Bob", "Brown", "50", "M"]],
|
||||||
|
["Mr. Bob Brown"]),
|
||||||
|
],
|
||||||
|
'phone_number': [
|
||||||
|
(["89991234567"], ["+7 (999) 123-45-67"]),
|
||||||
|
(["79991234567"], ["+7 (999) 123-45-67"]),
|
||||||
|
(["9991234567"], ["+7 (999) 123-45-67"]),
|
||||||
|
(["+7(999)123-45-67"], ["+7 (999) 123-45-67"]),
|
||||||
|
(["89991234567", "84951112233"],
|
||||||
|
["+7 (495) 111-22-33", "+7 (999) 123-45-67"]), # проверка сортировки
|
||||||
|
],
|
||||||
|
'my_sum_argv': [
|
||||||
|
(['1', '2'], '3'),
|
||||||
|
(['1', '2', '3', '4', '5'], '15'),
|
||||||
|
(['10', '20', '30'], '60'),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# Тестовые данные для проверки обработки ошибок
|
||||||
|
error_test_data = {
|
||||||
|
'fact_it': [
|
||||||
|
(0, "Error"), # n < 1
|
||||||
|
(-1, "Error"), # n < 1
|
||||||
|
(1000, "Error"), # n >= 1000
|
||||||
|
],
|
||||||
|
'fact_rec': [
|
||||||
|
(0, "Error"), # n < 1
|
||||||
|
(-5, "Error"), # n < 1
|
||||||
|
(1000, "Error"), # n >= 1000
|
||||||
|
],
|
||||||
|
'process_list': [
|
||||||
|
([], "Error"), # len(arr) < 1
|
||||||
|
(list(range(1, 1002)), "Error"), # len(arr) > 1000
|
||||||
|
],
|
||||||
|
'process_list_lc': [
|
||||||
|
([], "Error"), # len(arr) < 1
|
||||||
|
(list(range(1, 1002)), "Error"), # len(arr) > 1000
|
||||||
|
],
|
||||||
|
'process_list_gen': [
|
||||||
|
([], ["Error"]), # len(arr) < 1
|
||||||
|
(list(range(1, 1002)), ["Error"]), # len(arr) > 1000
|
||||||
|
],
|
||||||
|
'fibonacci': [
|
||||||
|
(0, "Error"), # n < 1
|
||||||
|
(-1, "Error"), # n < 1
|
||||||
|
(16, "Error"), # n > 15
|
||||||
|
(100, "Error"), # n > 15
|
||||||
|
],
|
||||||
|
'average_scores': [
|
||||||
|
([], "Error"), # X <= 0
|
||||||
|
([tuple([50.0] * 5) for _ in range(101)], "Error"), # X > 100
|
||||||
|
([tuple([50.0] * 101)], "Error"), # N > 100
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== Основные тесты ====================
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['fact_it'])
|
||||||
|
def test_fact_it(input_data, expected):
|
||||||
|
assert fact_it(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['fact_rec'])
|
||||||
|
def test_fact_rec(input_data, expected):
|
||||||
|
assert fact_rec(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['show_employee'])
|
||||||
|
def test_show_employee(input_data, expected):
|
||||||
|
assert show_employee(*input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['sum_and_sub'])
|
||||||
|
def test_sum_and_sub(input_data, expected):
|
||||||
|
assert sum_and_sub(*input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['process_list'])
|
||||||
|
def test_process_list(input_data, expected):
|
||||||
|
assert process_list(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['process_list'])
|
||||||
|
def test_process_list_lc(input_data, expected):
|
||||||
|
assert process_list_lc(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['process_list'])
|
||||||
|
def test_process_list_gen(input_data, expected):
|
||||||
|
assert list(process_list_gen(input_data)) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['my_sum'])
|
||||||
|
def test_my_sum(input_data, expected):
|
||||||
|
assert my_sum(*input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['email_validation'])
|
||||||
|
def test_email_validation(input_data, expected):
|
||||||
|
assert fun(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['fibonacci'])
|
||||||
|
def test_fibonacci(input_data, expected):
|
||||||
|
assert fibonacci(input_data) == expected
|
||||||
|
|
||||||
|
# Тесты для average_scores.py
|
||||||
|
def test_average_scores():
|
||||||
|
scores = [(89, 90, 78, 93, 80), (90, 91, 85, 88, 86), (91, 92, 83, 89, 90.5)]
|
||||||
|
result = compute_average_scores(scores)
|
||||||
|
assert len(result) == 5
|
||||||
|
assert abs(result[0] - 90.0) < 0.1
|
||||||
|
|
||||||
|
def test_average_scores_boundary_max():
|
||||||
|
scores = [tuple([50.0] * 100) for _ in range(100)] # граница: максимум X=100, N=100
|
||||||
|
result = compute_average_scores(scores)
|
||||||
|
assert result != "Error" and len(result) == 100
|
||||||
|
|
||||||
|
# Тесты для plane_angle.py
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['point_sub'])
|
||||||
|
def test_point_sub(input_data, expected):
|
||||||
|
p1 = Point(input_data[0], input_data[1], input_data[2])
|
||||||
|
p2 = Point(input_data[3], input_data[4], input_data[5])
|
||||||
|
p3 = p1 - p2
|
||||||
|
assert (p3.x, p3.y, p3.z) == expected
|
||||||
|
|
||||||
|
def test_point_dot():
|
||||||
|
p1 = Point(1, 2, 3)
|
||||||
|
p2 = Point(4, 5, 6)
|
||||||
|
assert p1.dot(p2) == 32
|
||||||
|
|
||||||
|
def test_point_cross():
|
||||||
|
p1 = Point(1, 0, 0)
|
||||||
|
p2 = Point(0, 1, 0)
|
||||||
|
p3 = p1.cross(p2)
|
||||||
|
assert (p3.x, p3.y, p3.z) == (0, 0, 1)
|
||||||
|
|
||||||
|
def test_point_absolute():
|
||||||
|
p = Point(3, 4, 0)
|
||||||
|
assert p.absolute() == 5.0
|
||||||
|
|
||||||
|
def test_point_zero_vector():
|
||||||
|
p = Point(0, 0, 0)
|
||||||
|
assert p.absolute() == 0.0
|
||||||
|
|
||||||
|
# Тесты для complex_numbers.py
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['complex_add'])
|
||||||
|
def test_complex_add(input_data, expected):
|
||||||
|
c1 = Complex(input_data[0], input_data[1])
|
||||||
|
c2 = Complex(input_data[2], input_data[3])
|
||||||
|
c3 = c1 + c2
|
||||||
|
assert (c3.real, c3.imaginary) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['complex_sub'])
|
||||||
|
def test_complex_sub(input_data, expected):
|
||||||
|
c1 = Complex(input_data[0], input_data[1])
|
||||||
|
c2 = Complex(input_data[2], input_data[3])
|
||||||
|
c3 = c1 - c2
|
||||||
|
assert (c3.real, c3.imaginary) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['complex_mul'])
|
||||||
|
def test_complex_mul(input_data, expected):
|
||||||
|
c1 = Complex(input_data[0], input_data[1])
|
||||||
|
c2 = Complex(input_data[2], input_data[3])
|
||||||
|
c3 = c1 * c2
|
||||||
|
assert (c3.real, c3.imaginary) == expected
|
||||||
|
|
||||||
|
def test_complex_div():
|
||||||
|
c1 = Complex(2, 1)
|
||||||
|
c2 = Complex(5, 6)
|
||||||
|
c3 = c1 / c2
|
||||||
|
assert abs(c3.real - 0.26) < 0.01 and abs(c3.imaginary - (-0.11)) < 0.01
|
||||||
|
|
||||||
|
def test_complex_mod():
|
||||||
|
c = Complex(2, 1)
|
||||||
|
m = c.mod()
|
||||||
|
assert abs(m.real - 2.236) < 0.01
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['complex_str'])
|
||||||
|
def test_complex_str(input_data, expected):
|
||||||
|
c = Complex(input_data[0], input_data[1])
|
||||||
|
assert str(c) == expected
|
||||||
|
|
||||||
|
# Тесты для circle_square_mk.py
|
||||||
|
|
||||||
|
def test_circle_square_mk_small():
|
||||||
|
result = circle_square_mk(1, 1000)
|
||||||
|
expected = math.pi
|
||||||
|
assert abs(result - expected) < 1.0
|
||||||
|
|
||||||
|
def test_circle_square_mk_large():
|
||||||
|
result = circle_square_mk(5, 10000)
|
||||||
|
expected = math.pi * 25
|
||||||
|
assert abs(result - expected) < 5.0
|
||||||
|
|
||||||
|
def test_circle_square_mk_zero_radius():
|
||||||
|
result = circle_square_mk(0, 100)
|
||||||
|
assert result == 0
|
||||||
|
|
||||||
|
def test_circle_square_mk_large_experiments():
|
||||||
|
result = circle_square_mk(10, 100000)
|
||||||
|
expected = math.pi * 100
|
||||||
|
assert abs(result - expected) < 10.0
|
||||||
|
|
||||||
|
# Тесты для process_list.py: граничные значения
|
||||||
|
|
||||||
|
def test_process_list_boundary_max():
|
||||||
|
arr = list(range(1, 1001)) # граница: максимум len(arr)=1000
|
||||||
|
result = process_list(arr)
|
||||||
|
assert result != "Error" and len(result) == 1000
|
||||||
|
|
||||||
|
def test_process_list_lc_boundary_max():
|
||||||
|
arr = list(range(1, 1001)) # граница: максимум len(arr)=1000
|
||||||
|
result = process_list_lc(arr)
|
||||||
|
assert result != "Error" and len(result) == 1000
|
||||||
|
|
||||||
|
def test_fibonacci_boundary_max():
|
||||||
|
result = fibonacci(15) # граница: максимум n=15
|
||||||
|
assert result != "Error" and len(result) == 15 and result[14] == 377
|
||||||
|
|
||||||
|
def test_fact_it_boundary_max():
|
||||||
|
result = fact_it(999) # граница: максимум n=999
|
||||||
|
assert result != "Error"
|
||||||
|
|
||||||
|
# ==================== Тесты обработки ошибок ====================
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['fact_it'])
|
||||||
|
def test_fact_it_errors(input_data, expected):
|
||||||
|
assert fact_it(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['fact_rec'])
|
||||||
|
def test_fact_rec_errors(input_data, expected):
|
||||||
|
assert fact_rec(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['process_list'])
|
||||||
|
def test_process_list_errors(input_data, expected):
|
||||||
|
assert process_list(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['process_list_lc'])
|
||||||
|
def test_process_list_lc_errors(input_data, expected):
|
||||||
|
assert process_list_lc(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['process_list_gen'])
|
||||||
|
def test_process_list_gen_errors(input_data, expected):
|
||||||
|
assert list(process_list_gen(input_data)) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['fibonacci'])
|
||||||
|
def test_fibonacci_errors(input_data, expected):
|
||||||
|
assert fibonacci(input_data) == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", error_test_data['average_scores'])
|
||||||
|
def test_average_scores_errors(input_data, expected):
|
||||||
|
assert compute_average_scores(input_data) == expected
|
||||||
|
|
||||||
|
# ==================== Тесты для people_sort.py ====================
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['people_sort'])
|
||||||
|
def test_people_sort(input_data, expected):
|
||||||
|
assert name_format(input_data) == expected
|
||||||
|
|
||||||
|
# ==================== Тесты для phone_number.py ====================
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['phone_number'])
|
||||||
|
def test_phone_number(input_data, expected):
|
||||||
|
assert sort_phone(input_data) == expected
|
||||||
|
|
||||||
|
# ==================== Тесты для log_decorator.py ====================
|
||||||
|
|
||||||
|
def test_log_decorator_writes_file(tmp_path):
|
||||||
|
log_file = str(tmp_path / "test.log")
|
||||||
|
|
||||||
|
@function_logger(log_file)
|
||||||
|
def add(a, b):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
result = add(2, 3)
|
||||||
|
assert result == 5
|
||||||
|
assert os.path.exists(log_file)
|
||||||
|
with open(log_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'add' in content
|
||||||
|
assert '(2, 3)' in content
|
||||||
|
assert '5' in content
|
||||||
|
|
||||||
|
def test_log_decorator_no_return(tmp_path):
|
||||||
|
log_file = str(tmp_path / "test.log")
|
||||||
|
|
||||||
|
@function_logger(log_file)
|
||||||
|
def noop():
|
||||||
|
pass
|
||||||
|
|
||||||
|
result = noop()
|
||||||
|
assert result is None
|
||||||
|
with open(log_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'noop' in content
|
||||||
|
assert '-' in content # None записывается как '-'
|
||||||
|
|
||||||
|
def test_log_decorator_kwargs(tmp_path):
|
||||||
|
log_file = str(tmp_path / "test.log")
|
||||||
|
|
||||||
|
@function_logger(log_file)
|
||||||
|
def greet(name="World"):
|
||||||
|
return f"Hello, {name}!"
|
||||||
|
|
||||||
|
result = greet(name="Test")
|
||||||
|
assert result == "Hello, Test!"
|
||||||
|
with open(log_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert 'greet' in content
|
||||||
|
assert "name" in content
|
||||||
|
|
||||||
|
def test_log_decorator_multiple_calls(tmp_path):
|
||||||
|
log_file = str(tmp_path / "test.log")
|
||||||
|
|
||||||
|
@function_logger(log_file)
|
||||||
|
def square(x):
|
||||||
|
return x * x
|
||||||
|
|
||||||
|
square(3)
|
||||||
|
square(5)
|
||||||
|
with open(log_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
assert content.count('square') == 2
|
||||||
|
|
||||||
|
# ==================== Тесты для my_sum_argv.py (subprocess) ====================
|
||||||
|
|
||||||
|
INTERPRETER = 'python'
|
||||||
|
|
||||||
|
def run_script(filename, args=None):
|
||||||
|
cmd = [INTERPRETER, filename] + (args if args else [])
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
|
||||||
|
return proc.stdout.strip()
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("input_data, expected", test_data['my_sum_argv'])
|
||||||
|
def test_my_sum_argv(input_data, expected):
|
||||||
|
assert run_script('my_sum_argv.py', input_data) == expected
|
||||||
|
|
||||||
|
# ==================== Тесты для file_search.py (subprocess) ====================
|
||||||
|
|
||||||
|
def test_file_search_found(tmp_path):
|
||||||
|
test_file = tmp_path / "hello.txt"
|
||||||
|
test_file.write_text("line1\nline2\nline3\n", encoding='utf-8')
|
||||||
|
result = subprocess.run(
|
||||||
|
[INTERPRETER, 'file_search.py', 'hello.txt'],
|
||||||
|
capture_output=True, text=True, check=False, cwd=str(tmp_path)
|
||||||
|
)
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
shutil.copy('file_search.py', str(tmp_path / 'file_search.py'))
|
||||||
|
result = subprocess.run(
|
||||||
|
[INTERPRETER, 'file_search.py', 'hello.txt'],
|
||||||
|
capture_output=True, text=True, check=False, cwd=str(tmp_path)
|
||||||
|
)
|
||||||
|
assert 'line1' in result.stdout
|
||||||
|
|
||||||
|
def test_file_search_not_found(tmp_path):
|
||||||
|
import shutil
|
||||||
|
shutil.copy('file_search.py', str(tmp_path / 'file_search.py'))
|
||||||
|
result = subprocess.run(
|
||||||
|
[INTERPRETER, 'file_search.py', 'nonexistent.txt'],
|
||||||
|
capture_output=True, text=True, check=False, cwd=str(tmp_path)
|
||||||
|
)
|
||||||
|
assert 'не найден' in result.stdout
|
||||||
|
|
||||||
|
def test_file_search_max_5_lines(tmp_path):
|
||||||
|
import shutil
|
||||||
|
shutil.copy('file_search.py', str(tmp_path / 'file_search.py'))
|
||||||
|
test_file = tmp_path / "many_lines.txt"
|
||||||
|
test_file.write_text('\n'.join([f"line{i}" for i in range(10)]), encoding='utf-8')
|
||||||
|
result = subprocess.run(
|
||||||
|
[INTERPRETER, 'file_search.py', 'many_lines.txt'],
|
||||||
|
capture_output=True, text=True, check=False, cwd=str(tmp_path)
|
||||||
|
)
|
||||||
|
lines = result.stdout.strip().split('\n')
|
||||||
|
assert len(lines) == 5 # выводит только первые 5 строк
|
||||||
|
|
||||||
|
# ==================== Тесты для files_sort.py (subprocess) ====================
|
||||||
|
|
||||||
|
def test_files_sort(tmp_path):
|
||||||
|
import shutil
|
||||||
|
test_dir = tmp_path / "data"
|
||||||
|
test_dir.mkdir()
|
||||||
|
(test_dir / "b.txt").write_text("", encoding='utf-8')
|
||||||
|
(test_dir / "a.txt").write_text("", encoding='utf-8')
|
||||||
|
(test_dir / "c.py").write_text("", encoding='utf-8')
|
||||||
|
shutil.copy('files_sort.py', str(tmp_path / 'files_sort.py'))
|
||||||
|
result = subprocess.run(
|
||||||
|
[INTERPRETER, 'files_sort.py', str(test_dir)],
|
||||||
|
capture_output=True, text=True, check=False, cwd=str(tmp_path)
|
||||||
|
)
|
||||||
|
lines = result.stdout.strip().split('\n')
|
||||||
|
# Сортировка: сначала по расширению, потом по имени
|
||||||
|
assert lines == ['c.py', 'a.txt', 'b.txt']
|
||||||
|
|
||||||
|
def test_files_sort_no_extension(tmp_path):
|
||||||
|
import shutil
|
||||||
|
test_dir = tmp_path / "data"
|
||||||
|
test_dir.mkdir()
|
||||||
|
(test_dir / "Makefile").write_text("", encoding='utf-8')
|
||||||
|
(test_dir / "a.txt").write_text("", encoding='utf-8')
|
||||||
|
shutil.copy('files_sort.py', str(tmp_path / 'files_sort.py'))
|
||||||
|
result = subprocess.run(
|
||||||
|
[INTERPRETER, 'files_sort.py', str(test_dir)],
|
||||||
|
capture_output=True, text=True, check=False, cwd=str(tmp_path)
|
||||||
|
)
|
||||||
|
lines = result.stdout.strip().split('\n')
|
||||||
|
# Файлы без расширения (ext='') идут первыми по sorted
|
||||||
|
assert lines[0] == 'Makefile'
|
||||||
51
labs/lab-1/app/app.py
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import random
|
||||||
|
from flask import Flask, render_template
|
||||||
|
from faker import Faker
|
||||||
|
|
||||||
|
fake = Faker('ru_RU')
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
application = app
|
||||||
|
|
||||||
|
images_ids = ['Pic1', 'Pic2', 'Pic3', 'Pic4', 'Pic5']
|
||||||
|
|
||||||
|
def generate_comments(replies=True):
|
||||||
|
comments = []
|
||||||
|
for i in range(random.randint(1, 3)):
|
||||||
|
comment = { 'author': fake.name(), 'text': fake.text() }
|
||||||
|
if replies:
|
||||||
|
comment['replies'] = generate_comments(replies=False)
|
||||||
|
comments.append(comment)
|
||||||
|
return comments
|
||||||
|
|
||||||
|
def generate_post(i):
|
||||||
|
return {
|
||||||
|
'title': fake.company(),
|
||||||
|
'text': fake.paragraph(nb_sentences=100),
|
||||||
|
'author': fake.name(),
|
||||||
|
'date': fake.date_time_between(start_date='-2y', end_date='now'),
|
||||||
|
'image_id': f'{images_ids[i]}.jpg',
|
||||||
|
'comments': generate_comments()
|
||||||
|
}
|
||||||
|
|
||||||
|
posts_list = sorted([generate_post(i) for i in range(5)], key=lambda p: p['date'], reverse=True)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
|
@app.route('/posts')
|
||||||
|
def posts():
|
||||||
|
return render_template('posts.html', title='Посты', posts=posts_list)
|
||||||
|
|
||||||
|
@app.route('/posts/<int:index>')
|
||||||
|
def post(index):
|
||||||
|
p = posts_list[index]
|
||||||
|
return render_template('post.html', title=p['title'], post=p)
|
||||||
|
|
||||||
|
@app.route('/about')
|
||||||
|
def about():
|
||||||
|
return render_template('about.html', title='Об авторе')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
||||||
BIN
labs/lab-1/app/static/images/Pic1.jpg
Normal file
|
After Width: | Height: | Size: 373 KiB |
BIN
labs/lab-1/app/static/images/Pic2.jpg
Normal file
|
After Width: | Height: | Size: 114 KiB |
BIN
labs/lab-1/app/static/images/Pic3.jpg
Normal file
|
After Width: | Height: | Size: 385 KiB |
BIN
labs/lab-1/app/static/images/Pic4.jpg
Normal file
|
After Width: | Height: | Size: 520 KiB |
BIN
labs/lab-1/app/static/images/Pic5.jpg
Normal file
|
After Width: | Height: | Size: 583 KiB |
BIN
labs/lab-1/app/static/images/avatar.jpg
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
labs/lab-1/app/static/images/example.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
504
labs/lab-1/app/static/styles.css
Normal file
|
|
@ -0,0 +1,504 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@400;600;700&family=Inter:wght@400;500;600&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary: #e63946;
|
||||||
|
--primary-light: #ff6b6b;
|
||||||
|
--primary-dark: #c1121f;
|
||||||
|
--primary-bg: rgba(230, 57, 70, 0.15);
|
||||||
|
--primary-glow: rgba(230, 57, 70, 0.3);
|
||||||
|
|
||||||
|
--bg-main: #0d0d0d;
|
||||||
|
--bg-darker: #080808;
|
||||||
|
--bg-card: #141414;
|
||||||
|
--bg-card-hover: #1a1a1a;
|
||||||
|
--bg-elevated: #1e1e1e;
|
||||||
|
--bg-input: #1a1a1a;
|
||||||
|
|
||||||
|
--text-primary: #ffffff;
|
||||||
|
--text-secondary: #b0b0b0;
|
||||||
|
--text-muted: #666666;
|
||||||
|
|
||||||
|
--border: #2a2a2a;
|
||||||
|
--border-light: #3a3a3a;
|
||||||
|
|
||||||
|
--radius-sm: 8px;
|
||||||
|
--radius-md: 12px;
|
||||||
|
--radius-lg: 16px;
|
||||||
|
--radius-xl: 24px;
|
||||||
|
|
||||||
|
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background-color: var(--bg-main);
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: clamp(2rem, 4vw, 3.5rem); }
|
||||||
|
h2 { font-size: clamp(1.5rem, 3vw, 2.5rem); }
|
||||||
|
h3 { font-size: clamp(1.25rem, 2.5vw, 1.75rem); }
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--primary-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: var(--bg-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--border);
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== NAVBAR ===== */
|
||||||
|
.navbar {
|
||||||
|
background: rgba(13, 13, 13, 0.8) !important;
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding: 1rem 0;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand:hover {
|
||||||
|
color: var(--primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .nav-link {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding: 0.5rem 1rem !important;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .nav-link:hover {
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
background: var(--primary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar .nav-link.active {
|
||||||
|
color: var(--primary) !important;
|
||||||
|
background: var(--primary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler {
|
||||||
|
border-color: var(--border) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler-icon {
|
||||||
|
filter: invert(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== MAIN CONTAINER ===== */
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.my-3 {
|
||||||
|
padding-top: 2rem;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== CARDS (posts list) ===== */
|
||||||
|
.card {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: var(--transition);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card:hover {
|
||||||
|
border-color: var(--border-light);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-top {
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-footer {
|
||||||
|
background: transparent;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
color: var(--text-muted) !important;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== BUTTONS ===== */
|
||||||
|
.btn-primary {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 4px 15px var(--primary-glow);
|
||||||
|
transition: var(--transition);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover,
|
||||||
|
.btn-primary:focus {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(230, 57, 70, 0.4);
|
||||||
|
background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary) 100%);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid var(--border-light);
|
||||||
|
color: var(--text-primary);
|
||||||
|
transition: var(--transition);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
background: var(--primary-bg);
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== POST PAGE ===== */
|
||||||
|
.post-header {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-meta span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
aspect-ratio: 16/9;
|
||||||
|
object-fit: cover;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== COMMENT FORM ===== */
|
||||||
|
.comment-form-section {
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-form-section h3 {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-form-section textarea {
|
||||||
|
background: var(--bg-input);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
transition: var(--transition);
|
||||||
|
width: 100%;
|
||||||
|
min-height: 120px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-form-section textarea:focus {
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 0 3px rgba(230, 57, 70, 0.15);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-form-section textarea::placeholder {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== COMMENTS ===== */
|
||||||
|
.comments-section h3 {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1.25rem 0;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-avatar {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
min-width: 48px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: var(--primary-bg);
|
||||||
|
color: var(--primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-body {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-author {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-text {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Replies */
|
||||||
|
.replies {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
border-left: 2px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
min-width: 36px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: var(--text-muted);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply .comment-author {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply .comment-text {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== ABOUT PAGE ===== */
|
||||||
|
.avatar {
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 3px solid var(--border);
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
box-shadow: 0 0 40px rgba(230, 57, 70, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== FOOTER ===== */
|
||||||
|
footer {
|
||||||
|
background: var(--bg-darker);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding: 2rem 0;
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-name {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-family: 'Raleway', sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-group {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-links a:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== INDEX PAGE ===== */
|
||||||
|
.task-list li {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-description .fw-bold {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-fluid.border {
|
||||||
|
border-color: var(--border) !important;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== RESPONSIVE ===== */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.post-meta {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-content {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comment-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
min-width: 40px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replies {
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
labs/lab-1/app/templates/about.html
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="mt-5 text-center">Об авторе</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<img class="avatar" src="{{ url_for('static', filename='images/avatar.jpg') }}" alt="Author">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8 text-justify d-flex align-items-center">
|
||||||
|
<p>
|
||||||
|
Деев Егор Викторович — backend-разработчик и Python-специалист из Москвы, специализирующийся на создании backend-решений, парсинге данных, автоматизации процессов и интеграции AI-компонентов.
|
||||||
|
<br><br>
|
||||||
|
Обучается в Московском Политехническом университете (МосПолитех), владеет широким технологическим стеком, включающим Python (Django, FastAPI, Flask), работу с базами данных (PostgreSQL, SQLite), DevOps-инструменты (Docker, Nginx), а также frontend-технологии (HTML5, CSS3, JavaScript).
|
||||||
|
<br><br>
|
||||||
|
Контакты для связи: email egor@deev.space, Telegram @Egor_Deev, GitHub @EDeev, личный сайт deev.space с блогом и портфолио проектов.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
71
labs/lab-1/app/templates/base.html
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" href="https://deev.space/media/favicon.ico" type="image/x-icon">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
|
|
||||||
|
<title>
|
||||||
|
{% if title %}
|
||||||
|
{{ title }}
|
||||||
|
{% else %}
|
||||||
|
Лабораторная работа №1
|
||||||
|
{% endif %}
|
||||||
|
</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||||
|
<div class="container">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('index') }}">Лабораторная работа № 1</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="{{ url_for('index') }}">Задание</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="{{ url_for('posts') }}">Посты</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ url_for('about') }}">Об авторе</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="my-1">
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}
|
||||||
|
<h1>Содержимое по умолчанию</h1>
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer-content">
|
||||||
|
<div>
|
||||||
|
<div class="footer-name">Деев Егор Викторович, ст. гр. 241-327</div>
|
||||||
|
<div class="footer-group">Московский Политехнический университет</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-links">
|
||||||
|
<a href="mailto:egor@deev.space">egor@deev.space</a>
|
||||||
|
<a href="https://deev.space" target="_blank">My Site</a>
|
||||||
|
<a href="https://t.me/Egor_Deev" target="_blank">Telegram</a>
|
||||||
|
<a href="https://github.com/EDeev" target="_blank">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
46
labs/lab-1/app/templates/index.html
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<h1 class="my-5">Задание к лабораторной работе</h1>
|
||||||
|
|
||||||
|
<p class="fw-bold">
|
||||||
|
1. Доработайте прилагаемое к заданию веб-приложение, добавив в него
|
||||||
|
шаблон post.html, отображающий содержимое поста.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>На странице должны присутствовать следующие элементы:</p>
|
||||||
|
|
||||||
|
<ul class="my-3">
|
||||||
|
<li>заголовок поста,</li>
|
||||||
|
<li>имя автора,</li>
|
||||||
|
<li>дата публикации,</li>
|
||||||
|
<li>изображение,</li>
|
||||||
|
<li>текст поста,</li>
|
||||||
|
<li>форма «Оставьте комментарий» с полем для ввода текста и кнопкой «Отправить»,</li>
|
||||||
|
<li>комментарии и ответы на них.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Все данные должны подставляться в шаблон из приложения.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://www.tutorialrepublic.com/twitter-bootstrap-tutorial/bootstrap-media-objects.php" target="_blank">Пример</a> того, как можно сделать отображение комментариев.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Пример возможного оформления страницы:</p>
|
||||||
|
|
||||||
|
<img class="img-fluid my-3 border" src="{{ url_for('static', filename='images/example.png') }}" alt="Post page example">
|
||||||
|
|
||||||
|
<p class="fw-bold">
|
||||||
|
2. Добавьте в базовый шаблон подвал (англ. footer) сайта. Укажите там ФИО и номер группы.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Выложите ваше веб-приложение на хостинг. В качестве ответа на соответствующее этой лабораторной работе задание в LMS
|
||||||
|
вам нужно указать ссылку на репозиторий с вашим решением и ссылку на ваше приложение на хостинге. В
|
||||||
|
случае, если репозиторий закрытый, -- не забудьте предоставить преподавателю доступ.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
56
labs/lab-1/app/templates/post.html
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row justify-content-center my-5">
|
||||||
|
<div class="col-lg-10">
|
||||||
|
<div class="post-header">
|
||||||
|
<h1 class="post-title">{{ post.title }}</h1>
|
||||||
|
<div class="post-meta">
|
||||||
|
<span>{{ post.author }}</span>
|
||||||
|
<span>{{ post.date.strftime('%d.%m.%Y') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img class="post-image" src="{{ url_for('static', filename='images/' + post.image_id) }}" alt="{{ post.title }}">
|
||||||
|
|
||||||
|
<div class="post-content">
|
||||||
|
{{ post.text }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comment-form-section">
|
||||||
|
<h3>Оставьте комментарий</h3>
|
||||||
|
<form method="POST">
|
||||||
|
<textarea name="comment" placeholder="Напишите ваш комментарий..." rows="4"></textarea>
|
||||||
|
<button type="submit" class="btn btn-primary mt-3">Отправить</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="comments-section">
|
||||||
|
<h3>Комментарии ({{ post.comments | length }})</h3>
|
||||||
|
{% for comment in post.comments %}
|
||||||
|
<div class="comment">
|
||||||
|
<div class="comment-avatar">{{ comment.author[0] }}</div>
|
||||||
|
<div class="comment-body">
|
||||||
|
<div class="comment-author">{{ comment.author }}</div>
|
||||||
|
<div class="comment-text">{{ comment.text }}</div>
|
||||||
|
|
||||||
|
{% if comment.replies %}
|
||||||
|
<div class="replies">
|
||||||
|
{% for reply in comment.replies %}
|
||||||
|
<div class="reply">
|
||||||
|
<div class="reply-avatar">{{ reply.author[0] }}</div>
|
||||||
|
<div class="comment-body">
|
||||||
|
<div class="comment-author">{{ reply.author }}</div>
|
||||||
|
<div class="comment-text">{{ reply.text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
25
labs/lab-1/app/templates/posts.html
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1 class="my-5">Последние посты</h1>
|
||||||
|
<div class="row mb-3">
|
||||||
|
{% for post in posts %}
|
||||||
|
<div class="col-md-6 d-flex">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<img class="card-img-top" src="{{ url_for('static', filename='images/' + post.image_id) }}" alt="Card image cap">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">{{ post.title }}</h2>
|
||||||
|
<p class="card-text">
|
||||||
|
{{ post.text | truncate(100) }}
|
||||||
|
</p>
|
||||||
|
<a href="{{ url_for('post', index=loop.index0) }}" class="btn btn-primary">Читать дальше →</a>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-muted">
|
||||||
|
Опубликовано {{ post.date.strftime('%d.%m.%Y') }}.
|
||||||
|
Автор: {{ post.author }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
9
labs/lab-1/requirements.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
click==8.0.4
|
||||||
|
Faker==13.3.2
|
||||||
|
Flask==2.0.3
|
||||||
|
itsdangerous==2.1.1
|
||||||
|
Jinja2==3.0.3
|
||||||
|
MarkupSafe==2.1.1
|
||||||
|
python-dateutil==2.8.2
|
||||||
|
six==1.16.0
|
||||||
|
Werkzeug==2.0.3
|
||||||