Compare commits
No commits in common. "fc869c60df8e3ff490349f3cd53396554fe38c47" and "main" have entirely different histories.
fc869c60df
...
main
162
.gitignore
vendored
162
.gitignore
vendored
@ -0,0 +1,162 @@
|
||||
# ---> 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/
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
[
|
||||
{
|
||||
"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]
|
||||
}
|
||||
]
|
||||
267
src/main.py
267
src/main.py
@ -1,267 +0,0 @@
|
||||
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()
|
||||
Loading…
Reference in New Issue
Block a user