Functional Weave
Code in Python

education.timetable-clash@1.0.0

impl/python.py

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) and not 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 not in 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)
        if not _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))
        if not _is_int(s.start) or s.start < 0 or s.start > 1439:
            raise ValueError('start must be a whole minute from 0 to 1439, received %s for "%s"' % (s.start, s.id))
        if not _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]