Skip to content

Instantly share code, notes, and snippets.

@okurka12
Created December 18, 2023 07:14
Show Gist options
  • Save okurka12/9aed395a2b637b5c577da2cd08d22434 to your computer and use it in GitHub Desktop.
Save okurka12/9aed395a2b637b5c577da2cd08d22434 to your computer and use it in GitHub Desktop.
decide which exams to skip to have calmer examination period
# 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()
@okurka12
Copy link
Author

Output:

Picked exams:
  IMA2  4. 1. (řádný)
  IAL   9. 1. (řádný)
  IFJ  12. 1. (řádný)
  ISS  15. 1. (řádný)
  INP  18. 1. (první opravný)
  IPT  22. 1. (první opravný)
Free days between exams:      [4, 2, 2, 2, 3]
Least number of free days:    2
Greatest number of free days: 4
Average number of free days:  2.6
Free days between last exam and summer semester:  14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment