2,288 bytes · the Python implementation · view raw
from typing import List, Sequence
from .education_timetable_clash_types import TimetableClash, TimetableSession
def _is_int(value: object) -> bool:
return isinstance(value, int) andnot isinstance(value, bool)
def _shared(a: Sequence[str], b: Sequence[str]) -> List[str]:
out: List[str] = []
for x in a:
if x in b and x notin out:
out.append(x)
return out
def timetable_clashes(sessions: Sequence[TimetableSession]) -> List[TimetableClash]:
"""Every pair of sessions that overlap on the same day and share a room, teacher or group. Times are half-open: a session ending at 600 does not clash with one starting at 600."""
ids = set()
for s in sessions:
if s.id in ids:
raise ValueError('session id "%s" appears twice' % (s.id,))
ids.add(s.id)
ifnot _is_int(s.day) or s.day < 1:
raise ValueError('day must be a whole number of 1 or more, received %s for "%s"' % (s.day, s.id))
ifnot _is_int(s.start) or s.start < 0or s.start > 1439:
raise ValueError('start must be a whole minute from 0 to 1439, received %s for "%s"' % (s.start, s.id))
ifnot _is_int(s.end) or s.end <= s.start or s.end > 1440:
raise ValueError(
'end must be a whole minute after start and no later than 1440, received %s for "%s"' % (s.end, s.id)
)
found = []
for i in range(len(sessions)):
for j in range(i + 1, len(sessions)):
a = sessions[i]
b = sessions[j]
if a.day != b.day:
continue
start = max(a.start, b.start)
end = min(a.end, b.end)
if start >= end:
continue
rooms = _shared(a.rooms, b.rooms)
teachers = _shared(a.teachers, b.teachers)
groups = _shared(a.groups, b.groups)
if len(rooms) + len(teachers) + len(groups) == 0:
continue
clash = TimetableClash(
first=a.id, second=b.id, day=a.day, start=start, end=end, rooms=rooms, teachers=teachers, groups=groups
)
found.append((a.day, start, i, j, clash))
found.sort(key=lambda f: (f[0], f[1], f[2], f[3]))
return [f[4] for f in found]