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/CalcFirstQuartile.py b/src/CalcFirstQuartile.py new file mode 100644 index 0000000..4af8958 --- /dev/null +++ b/src/CalcFirstQuartile.py @@ -0,0 +1,48 @@ +# -*- 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 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 new file mode 100644 index 0000000..0b57653 --- /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 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] diff --git a/test/test_XMLDataReader.py b/test/test_XMLDataReader.py new file mode 100644 index 0000000..f5a62fb --- /dev/null +++ b/test/test_XMLDataReader.py @@ -0,0 +1,51 @@ +# -*- 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]