summer_practice/divisions.py
2026-06-30 09:23:39 +03:00

180 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys # аргументы командной строки
import os # путь к main.py и имя csv-файла
import pandas as pd
import numpy as np
'''
Отдельный модуль: ДИВИЗИОНЫ и ПРАВИЛО ПОСЕВА победителей дивизионов.
Зачем он нужен.
Базовый main.py сортирует команды в конференции строго по проценту очков (%О)
и тай-брейкам. Но в части сезонов КХЛ в таблице конференции действовало правило:
ПОБЕДИТЕЛИ ДВУХ ДИВИЗИОНОВ получают места 1 и 2 в конференции, даже если по очкам
они не входят в топ-2. Из-за этого, например, в сезоне 2020/21 в Западной
конференции СКА (82 очка) стоит на сайте выше Динамо М (84) и Локомотива (83):
СКА выиграл дивизион Боброва, а те двое - из дивизиона Тарасова, который выиграл ЦСКА.
main.py этот посев не делает (в нём вообще нет деления на дивизионы, только
Запад/Восток), поэтому правило вынесено сюда, в отдельный файл, и main.py не трогается.
'''
'''
Расчёт таблицы берём из main.py, чтобы не дублировать логику (единственный источник
правды - там). main.py в конце сам вызывает main(), поэтому импортировать его напрямую
нельзя - он бы тут же попытался прочитать аргументы и упал. Поэтому читаем его исходник
и выполняем без последней строки `main()`: так получаем готовые функции build_standings
и словарь CONFERENCES, ничего при этом не запуская и не печатая.
'''
_main_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'main.py')
_src = open(_main_path, encoding='utf-8').read()
_src = _src.replace('\nmain()\n', '\n') # убираем автозапуск
_ns = {}
exec(compile(_src, 'main.py', 'exec'), _ns)
build_standings = _ns['build_standings']
CONFERENCES = _ns['CONFERENCES']
def make_div(bobrov, tarasov, kharlamov, chernyshev):
'''
Собрать словарь {команда -> дивизион} из четырёх списков команд.
Принимает: четыре списка - составы дивизионов Боброва и Тарасова (Запад),
Харламова и Чернышёва (Восток).
Возвращает: dict, где каждой команде сопоставлено название её дивизиона.
Аналог make_conf из main.py, только делений не два (Запад/Восток), а четыре.
'''
d = {}
d.update({t: 'Боброва' for t in bobrov}) # Запад
d.update({t: 'Тарасова' for t in tarasov}) # Запад
d.update({t: 'Харламова' for t in kharlamov}) # Восток
d.update({t: 'Чернышёва' for t in chernyshev}) # Восток
return d
'''
Состав дивизионов по сезонам (имена команд - ровно как в соответствующем csv).
Источники:
2020/21 - официальное распределение КХЛ;
2021/22 - Википедия "КХЛ в сезоне 2021/2022";
2018/19, 2019/20 - стандартная сетка дивизионов тех сезонов.
Для посева важен только победитель каждого дивизиона; выходные таблицы ниже
сверены с официальным сайтом КХЛ по всем четырём сезонам и совпадают.
'''
DIVISIONS = {
'2018_19': make_div(
bobrov=['СКА', 'Йокерит', 'Динамо М', 'Динамо Мн', 'Динамо Р', 'Слован'],
tarasov=['ЦСКА', 'Локомотив', 'ХК Сочи', 'Спартак', 'Витязь', 'Северсталь'],
kharlamov=['Автомобилист', 'Металлург Мг', 'Ак Барс', 'Трактор',
'Нефтехимик', 'Куньлунь РС', 'Адмирал'],
chernyshev=['Барыс', 'Авангард', 'Салават Юлаев', 'Сибирь',
'Торпедо НН', 'Амур'],
),
'2019_20': make_div(
bobrov=['СКА', 'Йокерит', 'Спартак', 'Динамо М', 'Динамо Мн', 'Динамо Р'],
tarasov=['ЦСКА', 'Локомотив', 'Витязь', 'Торпедо НН', 'ХК Сочи', 'Северсталь'],
kharlamov=['Ак Барс', 'Металлург Мг', 'Автомобилист', 'Нефтехимик',
'Трактор', 'Адмирал'],
chernyshev=['Авангард', 'Барыс', 'Салават Юлаев', 'Сибирь',
'Амур', 'Куньлунь РС'],
),
'2020_21': make_div(
bobrov=['СКА', 'Йокерит', 'Северсталь', 'Спартак', 'Витязь', 'ХК Сочи'],
tarasov=['ЦСКА', 'Динамо М', 'Локомотив', 'Динамо Мн', 'Динамо Р'],
kharlamov=['Автомобилист', 'Ак Барс', 'Металлург Мг', 'Нефтехимик',
'Трактор', 'Торпедо НН'],
chernyshev=['Авангард', 'Амур', 'Барыс', 'Куньлунь РС',
'Салават Юлаев', 'Сибирь'],
),
'2021_22': make_div(
bobrov=['Витязь', 'Йокерит', 'СКА', 'ХК Сочи', 'Торпедо', 'Спартак'],
tarasov=['Динамо М', 'Динамо Мн', 'Динамо Р', 'Локомотив', 'Северсталь', 'ЦСКА'],
kharlamov=['Автомобилист', 'Ак Барс', 'Куньлунь РС', 'Металлург Мг',
'Нефтехимик', 'Трактор'],
chernyshev=['Авангард', 'Амур', 'Барыс', 'Сибирь', 'Салават Юлаев', 'Адмирал'],
),
}
'''
В каких сезонах правило посева победителей дивизионов отражалось в таблице конференции.
2018/19, 2019/20, 2020/21 - да; в 2021/22 КХЛ перешла к чистому порядку по %О
(на сайте за 2021/22 победители дивизионов наверх НЕ поднимаются).
В сезонах 2018/19 и 2019/20 победители дивизионов и так были в топ-2 по очкам,
поэтому правило там виден результат не меняет - но оставляем его включённым,
чтобы логика была единой.
'''
SEED_BY_DIVISION = {
'2018_19': True,
'2019_20': True,
'2020_21': True,
'2021_22': False,
}
def seed_conference(conf_table):
'''
conf_table - часть итоговой таблицы по одной конференции, УЖЕ отсортированная
по критериям КХЛ (%О, затем В, ВО, ВБ, Р, ГЗ) - так её отдаёт build_standings.
Берём лучшего из каждого дивизиона (это и есть победитель дивизиона, т.к. таблица
отсортирована) и ставим этих двоих на места 1-2; между собой они идут в том же
порядке сортировки. Остальные команды - следом, тоже в порядке сортировки.
'''
# head(1) внутри каждого дивизиона = первая (а значит, лучшая) команда дивизиона.
winners = conf_table.groupby('Див', sort=False).head(1)
rest = conf_table.drop(winners.index)
return pd.concat([winners, rest])
def show_table(table, title):
'''
Напечатать таблицу конференции в консоль.
То же, что show_table в main.py, но дополнительно выводит столбец 'Див'
(дивизион) - чтобы было видно, почему победители дивизионов оказались наверху.
Принимает: table - готовая (уже посеянная) таблица; title - заголовок.
Ничего не возвращает - только печатает.
'''
# Тот же набор колонок, что на сайте КХЛ, плюс столбец дивизиона.
cols = ['Див', 'И', 'В', 'ВО', 'ВБ', 'ПБ', 'ПО', 'П', 'ГЗ', 'ГП', 'Р', 'О', '%О']
view = table[cols].copy()
view.insert(0, 'Место', np.arange(1, len(view) + 1))
view.insert(1, 'Команда', view.index)
print('\n' + '=' * 86)
print(title)
print('=' * 86)
print(view.to_string(index=False))
def main():
'''
Точка входа. Шаги:
1) определить сезон по имени CSV-файла;
2) построить таблицу команд через build_standings (логика из main.py);
3) приписать каждой команде дивизион (столбец 'Див');
4) для каждой конференции при необходимости применить посев победителей
дивизионов (seed_conference) и напечатать результат.
Пример: python3 divisions.py khl_3/khl_2020_21.csv
'''
path = sys.argv[1]
name = os.path.basename(path)
season = name.replace('khl_', '').replace('.csv', '')
df = pd.read_csv(path)
table = build_standings(df, CONFERENCES[season])
# Подписываем каждой команде её дивизион.
table['Див'] = pd.Series(table.index, index=table.index).map(DIVISIONS[season])
use_seeding = SEED_BY_DIVISION[season]
for conf in ['Запад', 'Восток']:
part = table[table['Конф'] == conf]
if use_seeding:
part = seed_conference(part)
suffix = ' (посев по дивизионам)' if use_seeding else ' (по очкам, без посева)'
show_table(part, 'Конференция ' + conf + ' ' + season + suffix)
main()