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]