From d1dcb71d0c3df54abc5a9fc953a341701f25cd9a Mon Sep 17 00:00:00 2001 From: Zerrant2 Date: Thu, 18 Dec 2025 01:52:00 +0300 Subject: [PATCH 1/4] Add new test --- data/data.xml | 13 ++++++++++ src/XMLDataReader.py | 29 ++++++++++++++++++++++ test/test_XMLDataReader.py | 50 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 data/data.xml create mode 100644 src/XMLDataReader.py create mode 100644 test/test_XMLDataReader.py diff --git a/data/data.xml b/data/data.xml new file mode 100644 index 0000000..0f93c59 --- /dev/null +++ b/data/data.xml @@ -0,0 +1,13 @@ + + + + 67 + 100 + 91 + + + 78 + 87 + 61 + + \ No newline at end of file diff --git a/src/XMLDataReader.py b/src/XMLDataReader.py new file mode 100644 index 0000000..d4e9015 --- /dev/null +++ b/src/XMLDataReader.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +import xml.etree.ElementTree as ET +from src.Types import DataType +from src.DataReader import DataReader + + +class XMLDataReader(DataReader): + def __init__(self) -> None: + self.key: str = "" + self.students: DataType = {} + + def read(self, path: str) -> DataType: + # Открываем и парсим XML файл + tree = ET.parse(path) + root = tree.getroot() + + # Проходим по всем элементам + for student_element in root.findall("student"): + self.key = student_element.get("name") + self.students[self.key] = [] + + # Проходим по всем предметам внутри студента + for subject_element in student_element.findall("subject"): + subj_name = subject_element.get("name") + # Текст внутри тега может содержать пробелы, чистим его + score = int(subject_element.text.strip()) + self.students[self.key].append((subj_name, score)) + + return self.students \ No newline at end of file diff --git a/test/test_XMLDataReader.py b/test/test_XMLDataReader.py new file mode 100644 index 0000000..1d315a0 --- /dev/null +++ b/test/test_XMLDataReader.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +import pytest +from src.Types import DataType +from src.XMLDataReader import XMLDataReader + + +class TestXMLDataReader: + + @pytest.fixture() + def file_and_data_content(self) -> tuple[str, DataType]: + # Данные для теста + xml_content = """ + + + 67 + 100 + 91 + + + 78 + 87 + 61 + +""" + + data = { + "Иванов Иван Иванович": [ + ("математика", 67), ("литература", 100), ("программирование", 91) + ], + "Петров Петр Петрович": [ + ("математика", 78), ("химия", 87), ("социология", 61) + ] + } + return xml_content, data + + @pytest.fixture() + def filepath_and_data(self, file_and_data_content: tuple[str, DataType], + tmpdir) -> tuple[str, DataType]: + # Создаем файл + p = tmpdir.mkdir("datadir").join("my_data.xml") + + # Пишем в бинарном режиме, чтобы не было проблем с кодировкой в Windows + p.write(file_and_data_content[0].encode('utf-8'), mode='wb') + + return str(p), file_and_data_content[1] + + def test_read(self, filepath_and_data: tuple[str, DataType]) -> None: + # Проверяем чтение + file_content = XMLDataReader().read(filepath_and_data[0]) + assert file_content == filepath_and_data[1] \ No newline at end of file From 9c0bdf0de2249a6a789a5249b59b63f82b7d22dd Mon Sep 17 00:00:00 2001 From: Zerrant2 Date: Thu, 18 Dec 2025 02:04:08 +0300 Subject: [PATCH 2/4] Fix --- src/TextDataReader.py | 7 ++++--- src/XMLDataReader.py | 2 +- test/test_XMLDataReader.py | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/TextDataReader.py b/src/TextDataReader.py index 216bd1d..84ea9ad 100644 --- a/src/TextDataReader.py +++ b/src/TextDataReader.py @@ -1,6 +1,7 @@ from src.DataReader import DataReader from src.Types import DataType + class TextDataReader(DataReader): def __init__(self) -> None: @@ -15,6 +16,6 @@ def read(self, path: str) -> DataType: self.students[self.key] = [] else: subj, score = line.split(":", maxsplit=1) - self.students[self.key].append( - (subj.strip(), int(score.strip()))) - return self.students # <--- ИСПРАВЛЕНИЕ: return должен быть на одном уровне с with + self.students[self.key].append((subj.strip(), + int(score.strip()))) + return self.students diff --git a/src/XMLDataReader.py b/src/XMLDataReader.py index d4e9015..0b57653 100644 --- a/src/XMLDataReader.py +++ b/src/XMLDataReader.py @@ -26,4 +26,4 @@ def read(self, path: str) -> DataType: score = int(subject_element.text.strip()) self.students[self.key].append((subj_name, score)) - return self.students \ No newline at end of file + return self.students diff --git a/test/test_XMLDataReader.py b/test/test_XMLDataReader.py index 1d315a0..f5a62fb 100644 --- a/test/test_XMLDataReader.py +++ b/test/test_XMLDataReader.py @@ -25,7 +25,8 @@ def file_and_data_content(self) -> tuple[str, DataType]: data = { "Иванов Иван Иванович": [ - ("математика", 67), ("литература", 100), ("программирование", 91) + ("математика", 67), ("литература", 100), + ("программирование", 91) ], "Петров Петр Петрович": [ ("математика", 78), ("химия", 87), ("социология", 61) @@ -47,4 +48,4 @@ def filepath_and_data(self, file_and_data_content: tuple[str, DataType], def test_read(self, filepath_and_data: tuple[str, DataType]) -> None: # Проверяем чтение file_content = XMLDataReader().read(filepath_and_data[0]) - assert file_content == filepath_and_data[1] \ No newline at end of file + assert file_content == filepath_and_data[1] From 07a2deb64eb0125471fad7b9154c911f8c51928e Mon Sep 17 00:00:00 2001 From: Zerrant2 Date: Thu, 18 Dec 2025 02:14:46 +0300 Subject: [PATCH 3/4] 2 tast --- src/CalcFirstQuartile.py | 47 ++++++++++++++++++++++++++++++++++ test/test_CalcFirstQuartile.py | 35 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/CalcFirstQuartile.py create mode 100644 test/test_CalcFirstQuartile.py diff --git a/src/CalcFirstQuartile.py b/src/CalcFirstQuartile.py new file mode 100644 index 0000000..8710015 --- /dev/null +++ b/src/CalcFirstQuartile.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from src.Types import DataType +from src.CalcRating import CalcRating + +# Определяем тип для словаря рейтингов: Имя -> Балл +RatingType = dict[str, float] + + +class CalcFirstQuartile: + + def __init__(self, data: DataType) -> None: + self.data: DataType = data + self.rating: RatingType = {} + + def calc(self) -> RatingType: + # Сначала считаем средний рейтинг всех студентов + # используем уже готовый класс CalcRating + full_rating = CalcRating(self.data).calc() + + # Собираем все баллы в список и сортируем их + values = sorted(full_rating.values()) + + # Если студентов нет, возвращаем пустоту + if not values: + return {} + + # Вычисляем порог первой квартили (25%) + # Это элемент, стоящий на позиции 25% от длины списка + # Например, если 4 студента, то index = 1 (второй по счету, если округлять вверх) + # Упрощенная формула: q1_index = len(values) // 4 + # Если нужно более точное (математическое) вычисление: + import math + q1_index = math.ceil(len(values) * 0.25) - 1 + + # Защита от отрицательного индекса + if q1_index < 0: + q1_index = 0 + + q1_value = values[q1_index] + + # Фильтруем студентов: берем тех, у кого рейтинг <= q1_value + filtered_students: RatingType = {} + for student, rating in full_rating.items(): + if rating <= q1_value: + filtered_students[student] = rating + + return filtered_students \ No newline at end of file diff --git a/test/test_CalcFirstQuartile.py b/test/test_CalcFirstQuartile.py new file mode 100644 index 0000000..a6e0408 --- /dev/null +++ b/test/test_CalcFirstQuartile.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from src.Types import DataType +from src.CalcFirstQuartile import CalcFirstQuartile +import pytest + +RatingType = dict[str, float] + + +class TestCalcFirstQuartile: + + @pytest.fixture() + def input_data(self) -> tuple[DataType, RatingType]: + data: DataType = { + "Студент 1 (Отличник)": [("m", 100), ("l", 100)], # avg 100 + "Студент 2 (Хорошист)": [("m", 80), ("l", 80)], # avg 80 + "Студент 3 (Троечник)": [("m", 60), ("l", 60)], # avg 60 + "Студент 4 (Двоечник)": [("m", 40), ("l", 40)], # avg 40 + } + + # Всего 4 студента. Первая квартиль (25%) - это нижние 25%. + # Сортированные баллы: 40, 60, 80, 100. + # Индекс 25%: 4 * 0.25 = 1 элемент (это 40). + # Значит, в первую квартиль попадает только "Двоечник" с баллом 40. + expected_students: RatingType = { + "Студент 4 (Двоечник)": 40.0 + } + + return data, expected_students + + def test_calc(self, input_data: tuple[DataType, RatingType]) -> None: + # Вызываем наш новый класс + quartile_students = CalcFirstQuartile(input_data[0]).calc() + + # Проверяем, что вернулся именно тот студент + assert quartile_students == input_data[1] From b9f11ce75d35087b52ef79a9f39c0e8d4386a231 Mon Sep 17 00:00:00 2001 From: Zerrant2 Date: Thu, 18 Dec 2025 02:16:32 +0300 Subject: [PATCH 4/4] 2 tast --- src/CalcFirstQuartile.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CalcFirstQuartile.py b/src/CalcFirstQuartile.py index 8710015..4af8958 100644 --- a/src/CalcFirstQuartile.py +++ b/src/CalcFirstQuartile.py @@ -26,7 +26,8 @@ def calc(self) -> RatingType: # Вычисляем порог первой квартили (25%) # Это элемент, стоящий на позиции 25% от длины списка - # Например, если 4 студента, то index = 1 (второй по счету, если округлять вверх) + # Например, если 4 студента, то index = 1 + # (второй по счету, если округлять вверх) # Упрощенная формула: q1_index = len(values) // 4 # Если нужно более точное (математическое) вычисление: import math @@ -44,4 +45,4 @@ def calc(self) -> RatingType: if rating <= q1_value: filtered_students[student] = rating - return filtered_students \ No newline at end of file + return filtered_students