"""The Schedule class: one day's bookings, never overlapping.
Version 1.1 adds a waitlist: request() books a slot if it is free and
otherwise queues it; cancel() then books waiting requests that now fit.
"""
from arena.adt import Queue
from arena.timeutil import overlaps
def _start_of(booking):
"""Sort key: a booking's start hour."""
return booking.start
class Schedule:
"""All bookings for one day. Invariant: no two bookings overlap."""
def __init__(self):
self._bookings = []
self._waitlist = Queue() # new in 1.1
def clash(self, new):
"""Return an existing booking that overlaps new, or None."""
for b in self._bookings:
if overlaps(b.start, b.end, new.start, new.end):
return b
return None
def add(self, booking):
"""Add a booking. Raise ValueError if it clashes with another."""
other = self.clash(booking)
if other is not None:
raise ValueError(f"{booking.group} clashes with {other.group}")
self._bookings.append(booking)
def request(self, booking):
"""Book if the time is free, otherwise waitlist it. Return True if booked."""
if self.clash(booking) is None:
self._bookings.append(booking)
return True
self._waitlist.enqueue(booking)
return False
def cancel(self, group):
"""Remove every booking for group, then try the waitlist.
Return how many bookings were removed.
"""
before = len(self._bookings)
self._bookings = [b for b in self._bookings if b.group != group]
self._fill_from_waitlist() # new in 1.1
return before - len(self._bookings)
def _fill_from_waitlist(self):
"""Book waiting requests that now fit, in the order they arrived."""
still_waiting = Queue()
while not self._waitlist.is_empty():
b = self._waitlist.dequeue()
if self.clash(b) is None:
self._bookings.append(b)
else:
still_waiting.enqueue(b)
self._waitlist = still_waiting
def waiting(self):
"""Return how many requests are on the waitlist."""
return len(self._waitlist)
def day_plan(self):
"""Return the bookings sorted by start time."""
return sorted(self._bookings, key=_start_of)
def revenue(self):
"""Return the total price of all bookings, in cents."""
total = 0
for b in self._bookings:
total += b.cost()
return total