Created
December 18, 2023 07:14
-
-
Save okurka12/9aed395a2b637b5c577da2cd08d22434 to your computer and use it in GitHub Desktop.
decide which exams to skip to have calmer examination period
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Author: víteček 🔥💯 (okurka12) | |
# | |
# The script tries to pick the right exams according to the defined criteria | |
# and it does so randomly | |
# | |
# exam dates are hard-coded below in the `exams` list | |
# | |
from random import shuffle | |
# Wishest thou to embark upon the logging endeavor? | |
LOGGING = False | |
FIRST = "řádný" | |
SECND = "první opravný" | |
THIRD = "druhý opravný" | |
# minimum number of free days between exams | |
MINB = 2 | |
# stop trying after how many iterations? | |
MAX_ITER = 20_000 | |
# a list of conditions that each exam must meet | |
CONDITIONS = [ | |
# no third terms | |
lambda e: e.term != THIRD, | |
# no exams after 19th of January | |
lambda e: e.month < 2 and e.day <= 22, | |
# skip INP exam on 2nd of January | |
lambda e: e.course != "INP" or e.day != 2 or e.month != 1 | |
] | |
# to how many places pad the course name in the exam's string representation | |
CNAME_PAD = 4 | |
# to how many places pad the month number in the exam's string representation | |
MPAD = 1 | |
# to how many places pad the day number in the exam's string representation | |
DPAD = 2 | |
# prefferably dont touch the below | |
################################################################################ | |
def log(*args, **kwargs) -> None: | |
"""use like `print`""" | |
if LOGGING: | |
print(*args, **kwargs) | |
class Exam: | |
def __init__(self, day: int, month: int, course: str, term: str) -> None: | |
self.day = day | |
self.month = month | |
self.course = course | |
self.term = term | |
def __sub__(self, other) -> int: | |
"""by how many days do the exam dates differ""" | |
assert isinstance(other, Exam), "incompatible type for subtraction " \ | |
"with class Exam" | |
if self.month == other.month: | |
return self.day - other.day | |
elif self.month == 2 and other.month == 1: | |
self_day = self.day + 31 | |
return self_day - other.day | |
elif self.month == 1 and other.month == 2: | |
return - (other - self) | |
else: | |
raise NotImplementedError | |
def __repr__(self) -> str: | |
# course name | |
cname = self.course.ljust(CNAME_PAD) | |
day = str(self.day).rjust(DPAD) | |
month = str(self.month).rjust(MPAD) | |
return f"{cname} {day}. {month}. ({self.term})" | |
def __lt__(self, other): | |
return self - other < other - self | |
exams = [ | |
# FIRST TERMS | |
Exam(2, 1, "INP", FIRST), | |
Exam(4, 1, "IMA2", FIRST), | |
Exam(9, 1, "IAL", FIRST), | |
Exam(11, 1, "IPT", FIRST), | |
Exam(12, 1, "IFJ", FIRST), | |
Exam(15, 1, "ISS", FIRST), | |
# SECOND TERMS | |
Exam(16, 1, "IMA2", SECND), | |
Exam(18, 1, "INP", SECND), | |
Exam(19, 1, "IFJ", SECND), | |
Exam(22, 1, "IPT", SECND), | |
Exam(23, 1, "IAL", SECND), | |
Exam(25, 1, "ISS", SECND), | |
# THIRD TERMS | |
Exam(26, 1, "IFJ", THIRD), | |
Exam(29, 1, "IMA2", THIRD), | |
Exam(30, 1, "INP", THIRD), | |
Exam(31, 1, "ISS", THIRD), | |
Exam(1, 2, "IAL", THIRD), | |
Exam(2, 2, "IPT", THIRD), | |
] | |
all_courses = set([exam.course for exam in exams]) | |
def pick_exams() -> list: | |
"""shuffles the exams and then picks one for each course""" | |
shuffle(exams) | |
picked_courses = set() | |
picked_exams = [] | |
# extend user defined conditions with "only have one exam per course" | |
conditions = [lambda e: e.course not in picked_courses] | |
conditions.extend(CONDITIONS) | |
# while len(picked_courses.symmetric_difference(all_courses)) != 0: | |
# for all the exam in the shuffled list | |
for exam in exams: | |
# if the exam meetn't all the conditions, skip it | |
if not all([c(exam) for c in conditions]): | |
continue | |
picked_courses.add(exam.course) | |
picked_exams.append(exam) | |
# assert that exam is picked for each course | |
assert len(picked_courses.symmetric_difference(all_courses)) == 0 | |
picked_exams.sort() | |
return picked_exams | |
def get_days_between(picked_exams: list) -> list: | |
"""for the picked exams, returns a list of free days between them""" | |
days_between = [] | |
for i in range(len(picked_exams) - 1): | |
days_between.append(picked_exams[i + 1] - picked_exams[i] - 1) | |
return days_between | |
def main() -> None: | |
cnt = 0 | |
while True: | |
cnt += 1 | |
picked_exams = pick_exams() | |
days_between = get_days_between(picked_exams) | |
# Free days after last exam | |
days_after = Exam(5, 2, "none", "none") - picked_exams[-1] | |
if min(days_between) >= MINB: | |
break | |
if cnt > MAX_ITER: | |
print("conditions are most likely unsatisfiable") | |
exit(1) | |
print("Picked exams:") | |
for exam in picked_exams: | |
print(f" {exam}") | |
print(f"Free days between exams: {days_between}") | |
print(f"Least number of free days: {min(days_between)}") | |
print(f"Greatest number of free days: {max(days_between)}") | |
print(f"Average number of free days: " | |
f"{sum(days_between)/len(days_between)}") | |
print(f"Free days between last exam and summer semester: ", days_after) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output: