All Works
C++ · Data Structures & Algorithms · May 2025

DataStructure Physiotherapy Center Scheduler

Source Code

Full C++ implementation on GitHub

View on GitHub →

Overview

A discrete-event simulation of a physiotherapy center, built as a Cairo University DSA course project. The system manages patient flow across three treatment types — electro-therapy, ultrasound-therapy, and gym exercises — while scheduling shared resources, enforcing appointment rules, and producing a detailed statistics report at the end of each run.

The project was implemented entirely in C++ from scratch, with no use of the STL. All data structures — stacks, queues, and priority queues — were built and owned by me, then composed into the scheduler logic.

Patient lifecycle

Every patient moves through a strict set of states driven by timestep comparisons. Arrival before appointment triggers the early list; arrival after incurs a waiting penalty before the patient is re-inserted into the correct queue.

IDLE
ERLY
WAIT
SERV
FNSH
IDLE
LATE → penalty wait →
WAIT
SERV
FNSH

A late patient with appointment time PT and arrival time VT incurs a penalty of (VT − PT) / 2 timesteps before joining a waiting list, but is sorted in that list by PT + penalty — preserving relative appointment order.

Data structure decisions

Each list in the system has a different access pattern, so each got a different structure. The constraint was that everything had to derive from the hand-rolled Stack, Queue, or Priority Queue base classes from the lab — no STL allowed.

List Structure Reason
ALL Patients LinkedQueue Input is sorted by arrival time; FIFO dequeue naturally processes patients in order.
Early Patients priQueue (min-heap by PT) Must always serve the earliest appointment first. Extended with a Reschedule() method that picks a random node and re-inserts with a new PT.
Late Patients priQueue (min-heap by PT+penalty) Same earliest-first priority, but effective priority is shifted by the late penalty.
E-Wait / U-Wait EU_Waitlist (sorted LinkedQueue) Sorted insertion by appointment time. Also exposes calcTreatmentLatency() for recovering patient routing.
X-Wait (gym) X_Waitlist Same sorted insertion, plus probabilistic cancellation — randomly picks and removes a patient if they meet the cancel criteria.
In-Treatment priQueue (min-heap by finish time) The patient whose treatment ends soonest always dequeues first, without scanning the whole list.
Finished Patients ArrayStack Output requires descending finish-time order. Since patients finish and are pushed onto the stack chronologically, popping naturally gives the most-recently-finished patient first.

Recovering vs. normal patients

Normal patients follow their treatment list in the exact input order. Recovering patients can take treatments in any order, so each time one is about to join a waiting list, the scheduler re-orders their treatment queue to minimise total waiting time.

The key insight: a recovering patient should start with the treatment whose waiting list currently has the lowest total treatment backlog (treatment latency). The organizeTreatmentList() function computes this live at the moment of each transition.

Scheduler.cpp — organizeTreatmentList() C++
void Scheduler::organizeTreatmentList(Patient* input)
{
    if (input->getType() == 0) return; // normal patient — skip

    LinkedQueue<Treatment*>& Temp = input->getTreatmentlist();
    if (Temp.isEmpty()) return;

    // Sample latency of each waiting list
    struct Latency { int latency; char type; };
    Latency arr[3] = {
        { E_Waiting.calcTreatmentLatency('E'), 'E' },
        { U_Waiting.calcTreatmentLatency('U'), 'U' },
        { X_Waiting.calcTreatmentLatency('X'), 'X' }
    };

    // 3-element insertion sort (always exactly 3 types)
    if (arr[0].latency > arr[1].latency) std::swap(arr[0], arr[1]);
    if (arr[1].latency > arr[2].latency) std::swap(arr[1], arr[2]);
    if (arr[0].latency > arr[1].latency) std::swap(arr[0], arr[1]);

    // Drain and re-enqueue in latency order
    int count = Temp.count();
    Treatment* treatments[3] = { nullptr, nullptr, nullptr };
    for (int i = 0; i < count; ++i) Temp.dequeue(treatments[i]);

    for (int i = 0; i < 3; ++i)
        for (int j = 0; j < count; ++j)
            if (treatments[j] && treatments[j]->getType() == arr[i].type) {
                Temp.enqueue(treatments[j]);
                treatments[j] = nullptr;
                break;
            }
}

Resource management

Electro and ultrasound devices are simple: one patient per device at a time, managed as a queue of available resources. Gym rooms are more involved — each room has a capacity, and multiple patients can share it simultaneously. A full room is removed from the available list and only returns when its first occupant leaves.

The Treatment base class handles this through two pure virtual methods: canAssign() checks resource availability, and moveToWait() dispatches the patient to the correct waiting list via the scheduler. Deriving ETherapy, UTherapy, and XTherapy from this base keeps the scheduler's assignment loop uniform across all three treatment types.

Probabilistic events

Two random events fire each timestep based on configurable probabilities loaded from the input file:

  • Cancellation (Pcancel): a random patient in the X-therapy waiting list, whose gym session is their last remaining treatment, may cancel and move directly to finished.
  • Rescheduling (Presc): a random patient in the early list may receive a new, later appointment time and be re-inserted into the early priority queue at their new position.
Both operations target a random node in their list, not necessarily the head — requiring direct node traversal of the underlying linked structure without copying the patient object.

Output and statistics

At simulation end, the program produces an output file listing every patient sorted by finish time in descending order, followed by aggregate statistics: average waiting and treatment times broken down by patient type, cancellation and rescheduling percentages, and the average late penalty. The ArrayStack of finished patients makes this ordering free — no post-sort needed.

The program supports two modes: interactive (pauses each timestep and prints all list states to console) and silent (runs to completion and writes only the output file).