Compare commits

...

No commits in common. "main" and "fc869c60df8e3ff490349f3cd53396554fe38c47" have entirely different histories.

6 changed files with 330 additions and 164 deletions

162
.gitignore vendored
View File

@ -1,162 +0,0 @@
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

View File

@ -1,2 +0,0 @@
# Zadanie1Toporov

25
data/report.json Normal file
View File

@ -0,0 +1,25 @@
{
"total_students": 6,
"average_grade_all": 82.83,
"top_student": {
"id": 4,
"name": "Елена Смирнова",
"courses": [
"Программирование на Python",
"Базы данных"
],
"grades": [
100.0,
98.0,
97.0
],
"avg_grade": 98.33333333333333
},
"course_popularity": {
"Математика": 3,
"Программирование на Python": 4,
"Физика": 3,
"Английский язык": 2,
"Базы данных": 1
}
}

38
data/students.json Normal file
View File

@ -0,0 +1,38 @@
[
{
"id": 1,
"name": "Иван Петров",
"courses": ["Математика", "Программирование на Python", "Физика"],
"grades": [85.0, 92.0, 78.0]
},
{
"id": 2,
"name": "Мария Сидорова",
"courses": ["Программирование на Python", "Английский язык"],
"grades": [95.0, 88.0, 91.0]
},
{
"id": 3,
"name": "Алексей Иванов",
"courses": ["Математика", "Физика"],
"grades": [72.0, 68.0, 74.0]
},
{
"id": 4,
"name": "Елена Смирнова",
"courses": ["Программирование на Python", "Базы данных"],
"grades": [100.0, 98.0, 97.0]
},
{
"id": 5,
"name": "Дмитрий Козлов",
"courses": ["Математика"],
"grades": [60.0, 65.0]
},
{
"id": 6,
"name": "Ольга Новикова",
"courses": ["Программирование на Python", "Английский язык", "Физика"],
"grades": [88.0, 94.0, 82.0, 90.0]
}
]

0
requirements.txt Normal file
View File

267
src/main.py Normal file
View File

@ -0,0 +1,267 @@
import json
import csv
import os
from typing import List, Dict, Any
# ---------- 1. Загрузка данных ----------
def load_students(filepath: str) -> List[Dict[str, Any]]:
"""
Загружает данные из файла (CSV или JSON).
Возвращает список словарей студентов.
"""
if not os.path.exists(filepath):
raise FileNotFoundError(f"Файл {filepath} не найден.")
if filepath.endswith('.json'):
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
# Приведение к единому формату (список словарей)
if isinstance(data, dict) and 'students' in data:
return data['students']
return data
elif filepath.endswith('.csv'):
students = []
with open(filepath, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
# Преобразование полей
student = {
'id': int(row['id']),
'name': row['name'],
'courses': row['courses'].split(';') if row['courses'] else [],
'grades': [float(x) for x in row['grades'].split(';')] if row['grades'] else []
}
students.append(student)
return students
else:
raise ValueError("Неподдерживаемый формат файла. Используйте .json или .csv")
# ---------- 2. Валидация одного студента ----------
def validate_student_data(student: Dict[str, Any]) -> bool:
"""
Проверяет корректность данных студента.
Возвращает True, если все поля валидны.
"""
required_keys = {'id', 'name', 'courses', 'grades'}
if not required_keys.issubset(student.keys()):
return False
# Проверка id
if not isinstance(student['id'], int) or student['id'] <= 0:
return False
# Проверка имени
if not isinstance(student['name'], str) or len(student['name'].strip()) == 0:
return False
# Проверка курсов (должен быть список строк)
if not isinstance(student['courses'], list):
return False
for course in student['courses']:
if not isinstance(course, str):
return False
# Проверка оценок (список чисел от 0 до 100)
if not isinstance(student['grades'], list):
return False
for grade in student['grades']:
if not isinstance(grade, (int, float)) or not (0 <= grade <= 100):
return False
return True
# ---------- 3. Средний балл ----------
def calculate_average_grade(grades: List[float]) -> float:
"""
Возвращает среднее арифметическое списка оценок.
Для пустого списка возвращает 0.0.
"""
if not grades:
return 0.0
return sum(grades) / len(grades)
# ---------- 4. Добавление студента ----------
def add_student(students: List[Dict[str, Any]], student: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
Добавляет нового студента, если id уникален.
Возвращает обновлённый список.
"""
if any(s['id'] == student['id'] for s in students):
print(f"Предупреждение: студент с id={student['id']} уже существует. Добавление отменено.")
return students
students.append(student)
return students
# ---------- 5. Удаление студента ----------
def remove_student(students: List[Dict[str, Any]], student_id: int) -> List[Dict[str, Any]]:
"""
Удаляет студента по id. Возвращает новый список.
"""
new_list = [s for s in students if s['id'] != student_id]
if len(new_list) == len(students):
print(f"Предупреждение: студент с id={student_id} не найден.")
return new_list
# ---------- 6. Топ-студенты по порогу ----------
def find_top_performers(students: List[Dict[str, Any]], threshold: float) -> List[Dict[str, Any]]:
"""
Возвращает список студентов со средним баллом >= threshold,
отсортированный по убыванию среднего балла.
"""
# Вычисляем средний балл для каждого студента
students_with_avg = []
for s in students:
avg = calculate_average_grade(s['grades'])
students_with_avg.append((avg, s))
# Фильтруем и сортируем
filtered = [(avg, s) for avg, s in students_with_avg if avg >= threshold]
filtered.sort(key=lambda x: x[0], reverse=True)
return [s for _, s in filtered]
# ---------- 7. Студенты по курсу ----------
def get_students_by_course(students: List[Dict[str, Any]], course_name: str) -> List[Dict[str, Any]]:
"""
Возвращает список студентов, изучающих указанный курс.
"""
return [s for s in students if course_name in s['courses']]
# ---------- 8. Генерация отчёта ----------
def generate_report(students: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Формирует статистический отчёт.
"""
if not students:
return {
"total_students": 0,
"average_grade_all": 0.0,
"top_student": None,
"course_popularity": {}
}
total = len(students)
# Средний балл всех студентов
all_avgs = [calculate_average_grade(s['grades']) for s in students]
avg_all = sum(all_avgs) / total if total else 0.0
# Лучший студент
max_avg = max(all_avgs)
top_student = next(s for s in students if calculate_average_grade(s['grades']) == max_avg)
# Популярность курсов
course_pop = {}
for s in students:
for c in s['courses']:
course_pop[c] = course_pop.get(c, 0) + 1
return {
"total_students": total,
"average_grade_all": round(avg_all, 2),
"top_student": top_student,
"course_popularity": course_pop
}
# ---------- 9. Сортировка студентов по среднему баллу ----------
def sort_students_by_grade(students: List[Dict[str, Any]], reverse: bool = False) -> List[Dict[str, Any]]:
"""
Возвращает новый список, отсортированный по среднему баллу.
reverse=True по убыванию, иначе по возрастанию.
"""
return sorted(students, key=lambda s: calculate_average_grade(s['grades']), reverse=reverse)
# ---------- 10. Сохранение отчёта в JSON ----------
def save_report(report: Dict[str, Any], filepath: str) -> None:
"""
Сохраняет отчёт в файл в формате JSON.
"""
# Преобразуем top_student в сериализуемый словарь (исключаем функции)
serializable_report = report.copy()
if 'top_student' in serializable_report and serializable_report['top_student'] is not None:
# Создаём копию без лишних полей
top = serializable_report['top_student'].copy()
serializable_report['top_student'] = top
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(serializable_report, f, indent=2, ensure_ascii=False)
# ---------- Основная функция main() ----------
def main():
# Убедимся, что папка data существует
os.makedirs("data", exist_ok=True)
data_file = "data/students.json"
# Если файла нет, создаём пример данных
if not os.path.exists(data_file):
example_students = [
{"id": 1, "name": "Иван Петров", "courses": ["Математика", "Программирование на Python", "Физика"], "grades": [85, 92, 78]},
{"id": 2, "name": "Мария Сидорова", "courses": ["Программирование на Python", "Английский язык"], "grades": [95, 88, 91]},
{"id": 3, "name": "Алексей Иванов", "courses": ["Математика", "Физика"], "grades": [72, 68, 74]},
{"id": 4, "name": "Елена Смирнова", "courses": ["Программирование на Python", "Базы данных"], "grades": [100, 98, 97]},
{"id": 5, "name": "Дмитрий Козлов", "courses": ["Математика"], "grades": [60, 65]}
]
with open(data_file, 'w', encoding='utf-8') as f:
json.dump(example_students, f, indent=2, ensure_ascii=False)
print(f"Создан пример файла {data_file}")
# 1. Загрузка
print("1. Загрузка данных...")
students = load_students(data_file)
print(f"Загружено студентов: {len(students)}")
# 2. Валидация и фильтрация некорректных
print("\n2. Валидация данных...")
valid_students = []
for s in students:
if validate_student_data(s):
valid_students.append(s)
else:
print(f"Предупреждение: студент {s.get('name', '?')} (id={s.get('id')}) содержит некорректные данные и будет пропущен.")
students = valid_students
print(f"После валидации осталось: {len(students)} студентов.")
# 3. Добавление поля avg_grade
print("\n3. Вычисление среднего балла для каждого студента...")
for s in students:
s['avg_grade'] = calculate_average_grade(s['grades'])
print(f"{s['name']}: средний балл = {s['avg_grade']:.2f}")
# 4. Топ-студенты с порогом 85.0
print("\n4. Топ-студенты (средний балл >= 85.0):")
top = find_top_performers(students, 85.0)
for s in top:
print(f" {s['name']}{s['avg_grade']:.2f}")
# 5. Студенты курса "Программирование на Python"
course_name = "Программирование на Python"
print(f"\n5. Студенты, изучающие курс «{course_name}»:")
python_students = get_students_by_course(students, course_name)
for s in python_students:
print(f" {s['name']}")
# 6. Генерация и сохранение отчёта
print("\n6. Генерация отчёта...")
report = generate_report(students)
report_path = "data/report.json"
save_report(report, report_path)
print(f"Отчёт сохранён в {report_path}")
# 7. Демонстрация add_student и remove_student
print("\n7. Демонстрация add_student и remove_student:")
new_student = {"id": 99, "name": "Тест Тестов", "courses": ["Программирование на Python"], "grades": [90, 85]}
students = add_student(students, new_student)
print(f"После добавления нового студента: {len(students)} студентов.")
students = remove_student(students, 2) # удалим Марию Сидорову
print(f"После удаления студента с id=2: {len(students)} студентов.")
# Восстановим удалённого для чистоты (необязательно)
students = add_student(students, {"id": 2, "name": "Мария Сидорова", "courses": ["Программирование на Python", "Английский язык"], "grades": [95, 88, 91]})
print(f"Восстановили Марию: {len(students)} студентов.")
# 8. Сортировка по среднему баллу и вывод первых 5
print("\n8. Первые 5 студентов при сортировке по убыванию среднего балла:")
sorted_students = sort_students_by_grade(students, reverse=True)
for s in sorted_students[:5]:
print(f" {s['name']}{calculate_average_grade(s['grades']):.2f}")
print("\nРабота программы завершена.")
if __name__ == "__main__":
main()