From 7e61bc0ec927417c112a870a70dd09c3e5cc1818 Mon Sep 17 00:00:00 2001 From: Shubhangam Singh Date: Thu, 14 May 2026 13:19:31 +0530 Subject: [PATCH 1/2] feat: add Round Robin CPU scheduling algorithm Implements preemptive Round Robin (RR) scheduling in C++17 following the TheAlgorithms/C-Plus-Plus code style. Key features: - Supports processes with different arrival times - Uses std::queue for the circular ready queue - Class template RoundRobin with addProcess() / scheduleForRR() / printResult() methods, wrapped in namespace cpu_scheduling_algorithms::round_robin - Doxygen comments on all types, functions, and parameters - 5 self-test cases validated with assert(), including: - Same-arrival-time scenario (quantum=2, three processes) - Single-process trivial case (no waiting, no preemption) - Burst == quantum case (no preemption, sequential completion) - Duplicate process ID guard - Different arrival times cross-validated against a reference impl --- .../round_robin_scheduling.cpp | 514 ++++++++++++++++++ 1 file changed, 514 insertions(+) create mode 100644 cpu_scheduling_algorithms/round_robin_scheduling.cpp diff --git a/cpu_scheduling_algorithms/round_robin_scheduling.cpp b/cpu_scheduling_algorithms/round_robin_scheduling.cpp new file mode 100644 index 00000000000..4a4e1401a78 --- /dev/null +++ b/cpu_scheduling_algorithms/round_robin_scheduling.cpp @@ -0,0 +1,514 @@ +/** + * @file + * @brief Implementation of Round Robin (RR) CPU scheduling algorithm + * @details + * Round Robin is a preemptive CPU scheduling algorithm where each process is + * assigned a fixed time slice called the "time quantum". Processes are placed + * in a circular queue and the CPU cycles through them. If a process does not + * finish within its quantum, it is preempted and placed at the back of the + * ready queue. This continues until all processes are complete. + * + * Key properties: + * - Preemptive: a running process can be interrupted after its quantum expires. + * - No starvation: every process is guaranteed CPU time at regular intervals. + * - Fair: CPU time is distributed equally across all processes. + * - Completion Time = time at which a process finishes execution. + * - Turnaround Time = Completion Time − Arrival Time + * - Waiting Time = Turnaround Time − Burst Time + * + * This implementation supports processes with **different arrival times**. + * + * @see https://en.wikipedia.org/wiki/Round-robin_scheduling + * @author [Shubhangam Singh](https://github.com/Shubhangam-Singh) + */ + +#include /// for std::sort +#include /// for assert +#include /// for std::setw, std::left +#include /// for std::cout, std::endl +#include /// for std::queue (circular ready queue) +#include /// for std::tuple, std::make_tuple, std::get +#include /// for uint32_t +#include /// for std::unordered_set (duplicate ID guard) +#include /// for std::vector + +using std::cout; +using std::endl; +using std::get; +using std::left; +using std::make_tuple; +using std::queue; +using std::tuple; +using std::unordered_set; +using std::vector; + +/** + * @namespace cpu_scheduling_algorithms + * @brief CPU scheduling algorithm implementations + */ +namespace cpu_scheduling_algorithms { + +/** + * @namespace round_robin + * @brief Functions and classes for the Round Robin scheduling algorithm + */ +namespace round_robin { + +/** + * @brief Comparator used to sort processes by arrival time, then by process ID. + * @tparam S Data type of Process ID + * @tparam T Data type of Arrival time + * @tparam E Data type of Burst time + * @param t1 First tuple (Process ID, Arrival Time, Burst Time) + * @param t2 Second tuple (Process ID, Arrival Time, Burst Time) + * @returns true if t1 should come before t2 in sorted order + * @returns false otherwise + */ +template +bool sortByArrival(const tuple& t1, const tuple& t2) { + if (get<1>(t1) != get<1>(t2)) { + return get<1>(t1) < get<1>(t2); // Earlier arrival first + } + return get<0>(t1) < get<0>(t2); // Smaller PID breaks tie +} + +/** + * @class RoundRobin + * @brief Implements the preemptive Round Robin CPU scheduling algorithm. + * + * Each process is represented as a 6-tuple: + * (Process ID, Arrival Time, Burst Time, + * Completion Time, Turnaround Time, Waiting Time) + * + * @tparam S Data type of Process ID (e.g., uint32_t) + * @tparam T Data type of Arrival time (e.g., uint32_t) + * @tparam E Data type of Burst time (e.g., uint32_t) + */ +template +class RoundRobin { + /** + * Input processes stored as 3-tuples: + * (Process ID, Arrival Time, Burst Time) + */ + vector> processes; + + /** + * Result vector — one 6-tuple per process after scheduling: + * (Process ID, Arrival Time, Burst Time, + * Completion Time, Turnaround Time, Waiting Time) + */ + vector> result; + + /** Set of process IDs already added — prevents duplicates. */ + unordered_set idList; + + /** Fixed CPU time slice (time quantum) in the same unit as burst time. */ + uint32_t quantum; + + public: + /** + * @brief Constructs a RoundRobin scheduler with the given time quantum. + * @param q Time quantum (must be ≥ 1) + */ + explicit RoundRobin(uint32_t q) : quantum(q) {} + + /** + * @brief Adds a process to the scheduler. + * + * Processes with duplicate IDs are silently ignored. + * + * @param id Unique process identifier + * @param arrival Time at which the process enters the ready queue + * @param burst Total CPU time required by the process + * @returns void + */ + void addProcess(S id, T arrival, E burst) { + if (idList.find(id) == idList.end()) { + processes.push_back(make_tuple(id, arrival, burst)); + idList.insert(id); + } + } + + /** + * @brief Runs the Round Robin scheduling algorithm. + * + * @details + * Algorithm overview: + * 1. Sort processes by arrival time (ties broken by process ID). + * 2. Maintain `remainingBurst[]` — copy of original burst times, decremented + * as each process executes. + * 3. Use a `std::queue` as the circular ready queue storing indices + * into the `processes` vector. + * 4. At every scheduler tick: + * a. Admit all processes whose arrival time ≤ current time to the queue. + * b. Dequeue the head process; execute it for min(remaining, quantum) units. + * c. Advance the clock. + * d. Admit any newly arrived processes (they arrived during the quantum). + * e. If the process still has remaining burst time, re-enqueue it. + * f. Otherwise record its completion, turnaround, and waiting times. + * 5. If the ready queue is empty but some processes have not arrived yet, + * fast-forward the clock to the next arrival. + * + * @returns A vector of 6-tuples with the final scheduling metrics for each + * process, in the same order as they were added. + */ + vector> scheduleForRR() { + uint32_t n = static_cast(processes.size()); + if (n == 0) { + return result; + } + + // Step 1: Sort processes by arrival time (ties broken by process ID) + std::sort(processes.begin(), processes.end(), + sortByArrival); + + // Track remaining burst time for each process (index-aligned) + vector remainingBurst(n); + for (uint32_t i = 0; i < n; i++) { + remainingBurst[i] = static_cast(get<2>(processes[i])); + } + + // Completion times recorded per-process + vector completionTime(n, 0.0); + + // Circular ready queue holds indices into `processes` + queue readyQueue; + + double t = 0.0; ///< Current simulation time + + // Index of the next process (by arrival order) not yet admitted + uint32_t nextToArrive = 0; + + // Admit processes that have already arrived at t=0 + while (nextToArrive < n && + static_cast(get<1>(processes[nextToArrive])) <= t) { + readyQueue.push(nextToArrive++); + } + + // Count finished processes + uint32_t finished = 0; + + while (finished < n) { + // If queue is empty, fast-forward to the next arrival + if (readyQueue.empty()) { + t = static_cast(get<1>(processes[nextToArrive])); + while (nextToArrive < n && + static_cast(get<1>(processes[nextToArrive])) <= + t) { + readyQueue.push(nextToArrive++); + } + } + + // Dequeue and execute the front process + uint32_t idx = readyQueue.front(); + readyQueue.pop(); + + // Execute for at most `quantum` units + double slice = std::min(remainingBurst[idx], + static_cast(quantum)); + remainingBurst[idx] -= slice; + t += slice; + + // Admit all processes that arrived during this quantum + while (nextToArrive < n && + static_cast(get<1>(processes[nextToArrive])) <= t) { + readyQueue.push(nextToArrive++); + } + + if (remainingBurst[idx] > 0.0) { + // Process not yet finished — re-enqueue at the back + readyQueue.push(idx); + } else { + // Process finished — record metrics + completionTime[idx] = t; + finished++; + } + } + + // Build the result vector (restore original insertion order) + result.resize(n); + for (uint32_t i = 0; i < n; i++) { + double arrival = static_cast(get<1>(processes[i])); + double burst = static_cast(get<2>(processes[i])); + double ct = completionTime[i]; + double tat = ct - arrival; + double wt = tat - burst; + + result[i] = make_tuple(get<0>(processes[i]), + get<1>(processes[i]), + get<2>(processes[i]), + ct, tat, wt); + } + return result; + } + + /** + * @brief Prints the scheduling table to standard output. + * @returns void + */ + void printResult() const { + cout << std::setw(17) << left << "Process ID" + << std::setw(17) << left << "Arrival Time" + << std::setw(17) << left << "Burst Time" + << std::setw(17) << left << "Completion Time" + << std::setw(17) << left << "Turnaround Time" + << std::setw(17) << left << "Waiting Time" << endl; + + for (const auto& p : result) { + cout << std::fixed << std::setprecision(2) + << std::setw(17) << left << get<0>(p) + << std::setw(17) << left << get<1>(p) + << std::setw(17) << left << get<2>(p) + << std::setw(17) << left << get<3>(p) + << std::setw(17) << left << get<4>(p) + << std::setw(17) << left << get<5>(p) << endl; + } + } +}; + +} // namespace round_robin +} // namespace cpu_scheduling_algorithms + +/** + * @brief Reference (brute-force) implementation used for test validation. + * + * Runs the same Round Robin algorithm step-by-step and returns the vector of + * 6-tuples so that assert() calls can compare against the class implementation. + * + * @param processes Vector of (PID, arrival, burst) tuples + * @param quantum Time quantum + * @returns Vector of (PID, arrival, burst, CT, TAT, WT) tuples + */ +static vector> +referenceRR(vector> processes, + uint32_t quantum) { + uint32_t n = static_cast(processes.size()); + + std::sort(processes.begin(), processes.end(), + cpu_scheduling_algorithms::round_robin::sortByArrival< + uint32_t, uint32_t, uint32_t>); + + vector rem(n); + for (uint32_t i = 0; i < n; i++) rem[i] = get<2>(processes[i]); + + vector ct(n, 0.0); + queue rq; + double t = 0.0; + uint32_t nxt = 0, done = 0; + + while (nxt < n && static_cast(get<1>(processes[nxt])) <= t) + rq.push(nxt++); + + while (done < n) { + if (rq.empty()) { + t = static_cast(get<1>(processes[nxt])); + while (nxt < n && + static_cast(get<1>(processes[nxt])) <= t) + rq.push(nxt++); + } + uint32_t idx = rq.front(); rq.pop(); + double slice = std::min(rem[idx], static_cast(quantum)); + rem[idx] -= slice; + t += slice; + while (nxt < n && + static_cast(get<1>(processes[nxt])) <= t) + rq.push(nxt++); + if (rem[idx] > 0.0) { + rq.push(idx); + } else { + ct[idx] = t; + done++; + } + } + + vector> res(n); + for (uint32_t i = 0; i < n; i++) { + double arrival = get<1>(processes[i]); + double burst = get<2>(processes[i]); + res[i] = make_tuple(get<0>(processes[i]), + get<1>(processes[i]), + get<2>(processes[i]), + ct[i], ct[i] - arrival, ct[i] - arrival - burst); + } + return res; +} + +/** + * @brief Self-test implementations + * + * Test 1 – Same arrival time (all at t=0), quantum=2: + * P1(burst=10), P2(burst=5), P3(burst=8) + * Traced results: P1 CT=23 WT=13, P2 CT=15 WT=10, P3 CT=21 WT=13 + * + * Test 2 – Single process (trivial): + * CT = arrival + burst, TAT = burst, WT = 0. + * + * Test 3 – All processes arrive at time 0, burst == quantum (no preemption): + * P1(5), P2(5), P3(5), q=5 -> CT: 5, 10, 15 + * + * Test 4 – Duplicate process ID: second addProcess() with same ID is a no-op. + * + * Test 5 – Different arrival times, quantum=3: + * P1(arr=0, burst=5), P2(arr=1, burst=3), P3(arr=2, burst=7) + * Cross-validated against an independent reference implementation. + * + * @returns void + */ +static void test() { + using namespace cpu_scheduling_algorithms::round_robin; + + // ----------------------------------------------------------------------- + // Test 1: Same arrival time (all at t=0), quantum=2 + // P1(burst=10), P2(burst=5), P3(burst=8) + // Execution trace: + // t=0-2: P1 runs (rem 8) + // t=2-4: P2 runs (rem 3) + // t=4-6: P3 runs (rem 6) + // t=6-8: P1 runs (rem 6) + // t=8-10: P2 runs (rem 1) + // t=10-12: P3 runs (rem 4) + // t=12-14: P1 runs (rem 4) + // t=14-15: P2 runs rem=0 -> P2 DONE CT[P2]=15 + // t=15-17: P3 runs (rem 2) + // t=17-19: P1 runs (rem 2) + // t=19-21: P3 runs rem=0 -> P3 DONE CT[P3]=21 + // t=21-23: P1 runs rem=0 -> P1 DONE CT[P1]=23 + // Results: P1 CT=23 TAT=23 WT=13 + // P2 CT=15 TAT=15 WT=10 + // P3 CT=21 TAT=21 WT=13 + // ----------------------------------------------------------------------- + { + RoundRobin scheduler(2); + scheduler.addProcess(1, 0, 10); + scheduler.addProcess(2, 0, 5); + scheduler.addProcess(3, 0, 8); + auto res = scheduler.scheduleForRR(); + + // After sorting by (arrival, PID): P1=index0, P2=index1, P3=index2 + // P1 + assert(get<3>(res[0]) == 23.0); // CT + assert(get<4>(res[0]) == 23.0); // TAT = CT - arrival(0) + assert(get<5>(res[0]) == 13.0); // WT = TAT - burst(10) + // P2 + assert(get<3>(res[1]) == 15.0); // CT + assert(get<4>(res[1]) == 15.0); // TAT + assert(get<5>(res[1]) == 10.0); // WT = TAT - burst(5) + // P3 + assert(get<3>(res[2]) == 21.0); // CT + assert(get<4>(res[2]) == 21.0); // TAT + assert(get<5>(res[2]) == 13.0); // WT = TAT - burst(8) + + // Waiting time is never negative + assert(get<5>(res[0]) >= 0.0); + assert(get<5>(res[1]) >= 0.0); + assert(get<5>(res[2]) >= 0.0); + } + + // ----------------------------------------------------------------------- + // Test 2: Single process — no preemption, no waiting + // ----------------------------------------------------------------------- + { + RoundRobin scheduler(4); + scheduler.addProcess(42, 3, 6); + auto res = scheduler.scheduleForRR(); + + assert(res.size() == 1); + assert(get<3>(res[0]) == 9.0); // CT = 3 + 6 + assert(get<4>(res[0]) == 6.0); // TAT = 6 + assert(get<5>(res[0]) == 0.0); // WT = 0 + } + + // ----------------------------------------------------------------------- + // Test 3: Burst time exactly equals quantum for every process + // No process is ever preempted; each finishes in one slot. + // All arrive at t=0, quantum=5. + // P1(5), P2(5), P3(5) -> CT: P1=5, P2=10, P3=15 + // ----------------------------------------------------------------------- + { + RoundRobin scheduler(5); + scheduler.addProcess(1, 0, 5); + scheduler.addProcess(2, 0, 5); + scheduler.addProcess(3, 0, 5); + auto res = scheduler.scheduleForRR(); + + assert(get<3>(res[0]) == 5.0); // P1 CT + assert(get<3>(res[1]) == 10.0); // P2 CT + assert(get<3>(res[2]) == 15.0); // P3 CT + + assert(get<5>(res[0]) == 0.0); // P1 WT + assert(get<5>(res[1]) == 5.0); // P2 WT + assert(get<5>(res[2]) == 10.0); // P3 WT + } + + // ----------------------------------------------------------------------- + // Test 4: Duplicate process ID — second add is a no-op + // ----------------------------------------------------------------------- + { + RoundRobin scheduler(3); + scheduler.addProcess(1, 0, 6); + scheduler.addProcess(1, 0, 99); // duplicate — must be ignored + auto res = scheduler.scheduleForRR(); + + assert(res.size() == 1); + assert(get<2>(res[0]) == 6); // burst must be the original 6 + } + + // ----------------------------------------------------------------------- + // Test 5: Different arrival times, quantum=3 + // P1(arr=0, burst=5), P2(arr=1, burst=3), P3(arr=2, burst=7) + // Cross-validate class result against reference implementation. + // ----------------------------------------------------------------------- + { + vector> procs = { + make_tuple(1u, 0u, 5u), + make_tuple(2u, 1u, 3u), + make_tuple(3u, 2u, 7u)}; + + auto expected = referenceRR(procs, 3); + + RoundRobin scheduler(3); + for (const auto& p : procs) { + scheduler.addProcess(get<0>(p), get<1>(p), get<2>(p)); + } + auto actual = scheduler.scheduleForRR(); + + assert(actual.size() == expected.size()); + for (size_t i = 0; i < actual.size(); i++) { + // Completion time must match + assert(get<3>(actual[i]) == get<3>(expected[i])); + // Turnaround time must match + assert(get<4>(actual[i]) == get<4>(expected[i])); + // Waiting time must match + assert(get<5>(actual[i]) == get<5>(expected[i])); + // Waiting time is never negative + assert(get<5>(actual[i]) >= 0.0); + } + } + + cout << "All tests have successfully passed!" << endl; +} + +/** + * @brief Entry point of the program + * @returns 0 on exit + */ +int main() { + test(); // run self-test implementations + + // ----------------------------------------------------------------------- + // Demo: GFG example — P1(burst=7), P2(burst=4), P3(burst=1), quantum=2 + // All processes arrive at t=0. + // Expected: P1 CT=12 WT=5 | P2 CT=9 WT=5 | P3 CT=5 WT=4 + // ----------------------------------------------------------------------- + cout << "\n--- Round Robin Scheduling Demo (quantum = 2) ---\n"; + cpu_scheduling_algorithms::round_robin::RoundRobin + demo(2); + demo.addProcess(1, 0, 7); + demo.addProcess(2, 0, 4); + demo.addProcess(3, 0, 1); + demo.scheduleForRR(); + demo.printResult(); + + return 0; +} From 537598a1c55b9a12f6fccb0e351eabccb6ba7c81 Mon Sep 17 00:00:00 2001 From: Shubhangam Singh Date: Thu, 14 May 2026 14:15:16 +0530 Subject: [PATCH 2/2] feat: add Priority CPU scheduling algorithm (non-preemptive and preemptive) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements both Non-Preemptive and Preemptive Priority Scheduling in C++17 following the TheAlgorithms/C-Plus-Plus code style. Convention: lower priority number = higher urgency (standard OS convention). Key features: - NonPreemptivePriority: event-driven, min-heap ready queue; running process is never preempted - PreemptivePriority: event-driven (jumps between arrivals and finish events); preempts current process when a higher-priority one arrives - Both classes: addProcess() / scheduleForNPP|PP() / printResult() - Wrapped in namespace cpu_scheduling_algorithms::priority_scheduling - Full Doxygen comments on all types, classes, and methods - 6 assert()-based self-tests: Test 1: Non-preemptive GFG example (different arrivals) — P1 WT=0, P2 WT=3, P3 WT=4 Test 2: Non-preemptive single process — WT=0 Test 3: Non-preemptive duplicate PID guard Test 4: Preemptive different arrivals — P1 CT=10, P2 CT=5, P3 CT=15 Test 5: Preemptive same arrival — P3(pri1) CT=6, P1(pri2) CT=13, P2(pri3) CT=17 Test 6: Preemptive single process — WT=0 --- .../priority_scheduling.cpp | 617 ++++++++++++++++++ 1 file changed, 617 insertions(+) create mode 100644 cpu_scheduling_algorithms/priority_scheduling.cpp diff --git a/cpu_scheduling_algorithms/priority_scheduling.cpp b/cpu_scheduling_algorithms/priority_scheduling.cpp new file mode 100644 index 00000000000..bf74b14105c --- /dev/null +++ b/cpu_scheduling_algorithms/priority_scheduling.cpp @@ -0,0 +1,617 @@ +/** + * @file + * @brief Implementation of Priority CPU scheduling algorithms + * @details + * Priority Scheduling selects the next process to run based on a numeric + * priority value. In this implementation a **lower priority number means + * higher urgency** (priority 1 runs before priority 2), matching the standard + * UNIX/Linux convention. + * + * Two variants are provided inside the same file: + * + * **Non-Preemptive Priority Scheduling** + * Once a process starts executing it runs to completion. When the CPU becomes + * free, the highest-priority process among all currently arrived processes is + * selected. Ties are broken first by arrival time, then by process ID. + * + * **Preemptive Priority Scheduling** + * The CPU can be taken away from the running process if a newly arriving + * process has a strictly higher priority (lower number). The preempted process + * returns to the ready queue with its remaining burst time. + * + * Metrics computed: + * - Completion Time (CT) = time at which a process finishes + * - Turnaround Time (TAT) = CT - Arrival Time + * - Waiting Time (WT) = TAT - Burst Time + * + * @see https://en.wikipedia.org/wiki/Scheduling_(computing)#Priority_scheduling + * @author [Shubhangam Singh](https://github.com/Shubhangam-Singh) + */ + +#include /// for std::sort +#include /// for assert +#include /// for uint32_t +#include /// for std::setw, std::left +#include /// for std::cout, std::endl +#include /// for std::numeric_limits +#include /// for std::tuple, std::make_tuple, std::get +#include /// for std::unordered_set +#include /// for std::vector +#include /// for std::priority_queue + +using std::cout; +using std::endl; +using std::get; +using std::left; +using std::make_tuple; +using std::priority_queue; +using std::tuple; +using std::unordered_set; +using std::vector; + +/** + * @namespace cpu_scheduling_algorithms + * @brief CPU scheduling algorithm implementations + */ +namespace cpu_scheduling_algorithms { + +/** + * @namespace priority_scheduling + * @brief Functions and classes for Priority CPU scheduling algorithms + */ +namespace priority_scheduling { + +/** + * @brief Comparator to sort input processes by arrival time; ties broken by + * priority (lower = higher urgency), then by process ID. + * @tparam S Process ID type + * @tparam T Arrival time type + * @tparam E Burst time type + * @tparam P Priority type + * @param a First process tuple (PID, arrival, burst, priority) + * @param b Second process tuple + * @returns true if @p a should come before @p b + * @returns false otherwise + */ +template +bool sortByArrival(const tuple& a, const tuple& b) { + if (get<1>(a) != get<1>(b)) { + return get<1>(a) < get<1>(b); // earlier arrival first + } + if (get<3>(a) != get<3>(b)) { + return get<3>(a) < get<3>(b); // lower priority number (higher urgency) + } + return get<0>(a) < get<0>(b); // smaller PID +} + +/** + * @class NonPreemptivePriority + * @brief Implements Non-Preemptive Priority CPU scheduling. + * + * Once a process starts it runs to completion. When the CPU becomes free, the + * highest-priority process (lowest priority number) among all arrived processes + * is selected. Ties are broken by arrival time, then process ID. + * + * Each process is a 4-tuple: (PID, Arrival Time, Burst Time, Priority) + * Each result is a 7-tuple: + * (PID, Arrival, Burst, Priority, Completion Time, Turnaround Time, Waiting + * Time) + * + * @tparam S Data type of Process ID + * @tparam T Data type of Arrival time + * @tparam E Data type of Burst time + * @tparam P Data type of Priority (lower number = higher urgency) + */ +template +class NonPreemptivePriority { + /** Input: (PID, arrival, burst, priority) */ + vector> processes; + + /** Result: (PID, arrival, burst, priority, CT, TAT, WT) */ + vector> result; + + /** Guard against duplicate process IDs */ + unordered_set idList; + + public: + /** + * @brief Adds a process to the scheduler. + * @param id Unique process identifier + * @param arrival Arrival time + * @param burst Total CPU burst time required + * @param priority Priority value (lower number = higher urgency) + * @returns void + */ + void addProcess(S id, T arrival, E burst, P priority) { + if (idList.find(id) == idList.end()) { + processes.emplace_back(id, arrival, burst, priority); + idList.insert(id); + } + } + + /** + * @brief Runs the Non-Preemptive Priority scheduling algorithm. + * + * @details Algorithm: + * 1. Sort processes by arrival time (ties: priority, then PID). + * 2. Maintain a min-heap ready queue ordered by (priority, arrival, index). + * 3. When the CPU is free, fast-forward to the next arriving process if the + * queue is empty, then pick the head of the heap. + * 4. Run the selected process to completion; record CT, TAT, WT. + * 5. After it finishes, admit all newly arrived processes to the heap and + * repeat. + * + * @returns Vector of 7-tuples with scheduling metrics (sorted by arrival). + */ + vector> scheduleForNPP() { + uint32_t n = static_cast(processes.size()); + if (n == 0) { + return result; + } + + std::sort(processes.begin(), processes.end(), sortByArrival); + + // Ready queue: (priority, arrival, sorted-index) — min-heap + using Entry = tuple; + auto cmp = [](const Entry& a, const Entry& b) { + if (get<0>(a) != get<0>(b)) return get<0>(a) > get<0>(b); + if (get<1>(a) != get<1>(b)) return get<1>(a) > get<1>(b); + return get<2>(a) > get<2>(b); + }; + priority_queue, decltype(cmp)> rq(cmp); + + result.resize(n); + double t = 0.0; + uint32_t nextToArrive = 0; + uint32_t done = 0; + + while (done < n) { + // Admit all processes that have arrived by time t + while (nextToArrive < n && + static_cast(get<1>(processes[nextToArrive])) <= t) { + rq.emplace(get<3>(processes[nextToArrive]), + get<1>(processes[nextToArrive]), nextToArrive); + nextToArrive++; + } + + // If nothing is ready, fast-forward to the next arrival + if (rq.empty()) { + t = static_cast(get<1>(processes[nextToArrive])); + continue; + } + + // Pick the highest-priority arrived process + auto [pri, arr, idx] = rq.top(); + rq.pop(); + + double arrival = static_cast(get<1>(processes[idx])); + double burst = static_cast(get<2>(processes[idx])); + + t += burst; // non-preemptive: run to completion + double ct = t; + double tat = ct - arrival; + double wt = tat - burst; + + result[idx] = make_tuple(get<0>(processes[idx]), + get<1>(processes[idx]), + get<2>(processes[idx]), + get<3>(processes[idx]), + ct, tat, wt); + done++; + } + return result; + } + + /** + * @brief Prints the scheduling table to standard output. + * @returns void + */ + void printResult() const { + cout << std::setw(14) << left << "Process ID" + << std::setw(14) << left << "Arrival" + << std::setw(14) << left << "Burst" + << std::setw(14) << left << "Priority" + << std::setw(18) << left << "Completion Time" + << std::setw(18) << left << "Turnaround Time" + << std::setw(14) << left << "Waiting Time" << endl; + for (const auto& p : result) { + cout << std::fixed << std::setprecision(2) + << std::setw(14) << left << get<0>(p) + << std::setw(14) << left << get<1>(p) + << std::setw(14) << left << get<2>(p) + << std::setw(14) << left << get<3>(p) + << std::setw(18) << left << get<4>(p) + << std::setw(18) << left << get<5>(p) + << std::setw(14) << left << get<6>(p) << endl; + } + } +}; + +// --------------------------------------------------------------------------- + +/** + * @class PreemptivePriority + * @brief Implements Preemptive Priority CPU scheduling. + * + * When a new process arrives with a strictly higher priority (lower number) + * than the currently running process, the CPU is immediately taken away and + * given to the new process. The preempted process re-enters the ready queue + * with its remaining burst time. + * + * Uses an event-driven simulation: events are process arrivals and process + * completions, so the clock jumps directly between events without simulating + * every unit of time. + * + * @tparam S Data type of Process ID + * @tparam T Data type of Arrival time + * @tparam E Data type of Burst time + * @tparam P Data type of Priority (lower number = higher urgency) + */ +template +class PreemptivePriority { + /** Input: (PID, arrival, burst, priority) */ + vector> processes; + + /** Result: (PID, arrival, burst, priority, CT, TAT, WT) */ + vector> result; + + /** Guard against duplicate process IDs */ + unordered_set idList; + + public: + /** + * @brief Adds a process to the scheduler. + * @param id Unique process identifier + * @param arrival Arrival time + * @param burst Total CPU burst time required + * @param priority Priority value (lower number = higher urgency) + * @returns void + */ + void addProcess(S id, T arrival, E burst, P priority) { + if (idList.find(id) == idList.end()) { + processes.emplace_back(id, arrival, burst, priority); + idList.insert(id); + } + } + + /** + * @brief Runs the Preemptive Priority scheduling algorithm. + * + * @details Event-driven algorithm: + * 1. Sort processes by arrival time. + * 2. Maintain `rem[]` = remaining burst times, and a min-heap ready queue. + * 3. At each iteration: + * a. Admit all processes whose arrival <= current time t. + * b. If CPU is idle and queue is empty, advance t to the next arrival. + * c. Check if a higher-priority process in the queue should preempt the + * current one. If so, push the current process back and switch. + * d. Compute the next event: min(next arrival time, finish time of + * current process). Run until that event; advance t. + * e. If the current process finishes, record its CT, TAT, WT. + * + * @returns Vector of 7-tuples with scheduling metrics (sorted by arrival). + */ + vector> scheduleForPP() { + uint32_t n = static_cast(processes.size()); + if (n == 0) { + return result; + } + + std::sort(processes.begin(), processes.end(), sortByArrival); + + vector rem(n); + for (uint32_t i = 0; i < n; i++) { + rem[i] = static_cast(get<2>(processes[i])); + } + vector ct(n, 0.0); + + // Ready queue: (priority, arrival, sorted-index) + using Entry = tuple; + auto cmp = [](const Entry& a, const Entry& b) { + if (get<0>(a) != get<0>(b)) return get<0>(a) > get<0>(b); + if (get<1>(a) != get<1>(b)) return get<1>(a) > get<1>(b); + return get<2>(a) > get<2>(b); + }; + priority_queue, decltype(cmp)> rq(cmp); + + const double INF = std::numeric_limits::infinity(); + double t = 0.0; + uint32_t nextToArrive = 0; + uint32_t done = 0; + int32_t current = -1; ///< index of currently running process (-1 = idle) + + while (done < n) { + // 1. Admit all arrived processes + while (nextToArrive < n && + static_cast(get<1>(processes[nextToArrive])) <= t) { + rq.emplace(get<3>(processes[nextToArrive]), + get<1>(processes[nextToArrive]), nextToArrive); + nextToArrive++; + } + + // 2. If idle and nothing ready, fast-forward to next arrival + if (current == -1 && rq.empty()) { + t = static_cast(get<1>(processes[nextToArrive])); + continue; + } + + // 3. Check for preemption or initial assignment + if (!rq.empty()) { + P topPri = get<0>(rq.top()); + P curPri = (current == -1) + ? std::numeric_limits

::max() + : get<3>(processes[current]); + if (topPri < curPri) { + // Higher-priority process available — preempt or assign + if (current != -1) { + // Push current back with updated remaining burst + // (We track rem[] separately, so just push the entry) + rq.emplace(get<3>(processes[current]), + get<1>(processes[current]), + static_cast(current)); + } + auto [p, a, idx] = rq.top(); + rq.pop(); + current = static_cast(idx); + } + } + + // 4. Determine next event time + double nextArrivalTime = (nextToArrive < n) + ? static_cast( + get<1>(processes[nextToArrive])) + : INF; + double nextFinishTime = t + rem[current]; + double nextEvent = std::min(nextArrivalTime, nextFinishTime); + + // 5. Advance clock and reduce remaining burst + double elapsed = nextEvent - t; + rem[current] -= elapsed; + t = nextEvent; + + // 6. Check if current process has finished + if (rem[current] <= 0.0) { + ct[current] = t; + done++; + current = -1; + } + } + + // Build result vector (preserves sorted-by-arrival order) + result.resize(n); + for (uint32_t i = 0; i < n; i++) { + double arrival = static_cast(get<1>(processes[i])); + double burst = static_cast(get<2>(processes[i])); + double tat = ct[i] - arrival; + double wt = tat - burst; + result[i] = make_tuple(get<0>(processes[i]), get<1>(processes[i]), + get<2>(processes[i]), get<3>(processes[i]), + ct[i], tat, wt); + } + return result; + } + + /** + * @brief Prints the scheduling table to standard output. + * @returns void + */ + void printResult() const { + cout << std::setw(14) << left << "Process ID" + << std::setw(14) << left << "Arrival" + << std::setw(14) << left << "Burst" + << std::setw(14) << left << "Priority" + << std::setw(18) << left << "Completion Time" + << std::setw(18) << left << "Turnaround Time" + << std::setw(14) << left << "Waiting Time" << endl; + for (const auto& p : result) { + cout << std::fixed << std::setprecision(2) + << std::setw(14) << left << get<0>(p) + << std::setw(14) << left << get<1>(p) + << std::setw(14) << left << get<2>(p) + << std::setw(14) << left << get<3>(p) + << std::setw(18) << left << get<4>(p) + << std::setw(18) << left << get<5>(p) + << std::setw(14) << left << get<6>(p) << endl; + } + } +}; + +} // namespace priority_scheduling +} // namespace cpu_scheduling_algorithms + +// --------------------------------------------------------------------------- +// Self-tests +// --------------------------------------------------------------------------- + +/** + * @brief Self-test implementations + * + * Test 1 – Non-preemptive, GFG example (different arrival times): + * P1(arr=0, burst=4, pri=2), P2(arr=1, burst=2, pri=1), P3(arr=2,burst=6,pri=3) + * Expected: P1 CT=4 WT=0, P2 CT=6 WT=3, P3 CT=12 WT=4 + * + * Test 2 – Non-preemptive, single process: + * CT = arrival + burst, TAT = burst, WT = 0 + * + * Test 3 – Non-preemptive, duplicate PID guard. + * + * Test 4 – Preemptive, different arrival times (from GFG screenshot): + * P1(arr=0,burst=6,pri=2), P2(arr=1,burst=4,pri=1), P3(arr=2,burst=5,pri=3) + * Expected: P1 CT=10 WT=4, P2 CT=5 WT=0, P3 CT=15 WT=8 + * + * Test 5 – Preemptive, same arrival times: + * P1(arr=0,burst=7,pri=2), P2(arr=0,burst=4,pri=3), P3(arr=0,burst=6,pri=1) + * P3(pri=1) runs first → CT=6; P1(pri=2) next → CT=13; P2(pri=3) last → CT=17 + * + * Test 6 – Preemptive, single process: WT = 0. + * + * @returns void + */ +static void test() { + using namespace cpu_scheduling_algorithms::priority_scheduling; + + // ------------------------------------------------------------------- + // Test 1: Non-preemptive, GFG example + // Trace: t=0 P1 starts (only arrived process); P1 finishes at t=4. + // t=4 rq={P2(pri1),P3(pri3)} → P2 runs; P2 finishes at t=6. + // t=6 rq={P3} → P3 runs; P3 finishes at t=12. + // ------------------------------------------------------------------- + { + NonPreemptivePriority sched; + sched.addProcess(1, 0, 4, 2); + sched.addProcess(2, 1, 2, 1); + sched.addProcess(3, 2, 6, 3); + auto res = sched.scheduleForNPP(); + + // Sorted by arrival: P1=idx0, P2=idx1, P3=idx2 + assert(get<4>(res[0]) == 4.0); // P1 CT + assert(get<5>(res[0]) == 4.0); // P1 TAT + assert(get<6>(res[0]) == 0.0); // P1 WT + + assert(get<4>(res[1]) == 6.0); // P2 CT + assert(get<5>(res[1]) == 5.0); // P2 TAT + assert(get<6>(res[1]) == 3.0); // P2 WT + + assert(get<4>(res[2]) == 12.0); // P3 CT + assert(get<5>(res[2]) == 10.0); // P3 TAT + assert(get<6>(res[2]) == 4.0); // P3 WT + + // Waiting time is never negative + for (const auto& p : res) assert(get<6>(p) >= 0.0); + } + + // ------------------------------------------------------------------- + // Test 2: Non-preemptive, single process + // ------------------------------------------------------------------- + { + NonPreemptivePriority sched; + sched.addProcess(7, 5, 3, 1); + auto res = sched.scheduleForNPP(); + + assert(res.size() == 1); + assert(get<4>(res[0]) == 8.0); // CT = 5 + 3 + assert(get<5>(res[0]) == 3.0); // TAT = 3 + assert(get<6>(res[0]) == 0.0); // WT = 0 + } + + // ------------------------------------------------------------------- + // Test 3: Non-preemptive, duplicate PID — second add is a no-op + // ------------------------------------------------------------------- + { + NonPreemptivePriority sched; + sched.addProcess(1, 0, 5, 1); + sched.addProcess(1, 0, 99, 1); // duplicate + auto res = sched.scheduleForNPP(); + + assert(res.size() == 1); + assert(get<2>(res[0]) == 5); // original burst preserved + } + + // ------------------------------------------------------------------- + // Test 4: Preemptive, different arrival times (GFG screenshot) + // Trace: + // t=0: P1 admitted, runs. rem[P1]=6 + // t=1: P2 arrives (pri=1) < P1(pri=2) → preempt. P2 runs. + // t=2: P3 arrives (pri=3) > P2(pri=1) → no preemption. P2 keeps running. + // t=5: P2 finishes (CT=5). rq={P1(rem5),P3}. P1(pri=2) < P3(pri=3) → P1 runs. + // t=10: P1 finishes (CT=10). P3 runs. + // t=15: P3 finishes (CT=15). + // ------------------------------------------------------------------- + { + PreemptivePriority sched; + sched.addProcess(1, 0, 6, 2); + sched.addProcess(2, 1, 4, 1); + sched.addProcess(3, 2, 5, 3); + auto res = sched.scheduleForPP(); + + // Sorted by arrival: P1=idx0, P2=idx1, P3=idx2 + assert(get<4>(res[0]) == 10.0); // P1 CT + assert(get<6>(res[0]) == 4.0); // P1 WT + + assert(get<4>(res[1]) == 5.0); // P2 CT + assert(get<6>(res[1]) == 0.0); // P2 WT + + assert(get<4>(res[2]) == 15.0); // P3 CT + assert(get<6>(res[2]) == 8.0); // P3 WT + + for (const auto& p : res) assert(get<6>(p) >= 0.0); + } + + // ------------------------------------------------------------------- + // Test 5: Preemptive, same arrival time + // P3(pri=1) highest → runs 0-6 (CT=6). + // P1(pri=2) next → runs 6-13 (CT=13). + // P2(pri=3) last → runs 13-17 (CT=17). + // ------------------------------------------------------------------- + { + PreemptivePriority sched; + sched.addProcess(1, 0, 7, 2); + sched.addProcess(2, 0, 4, 3); + sched.addProcess(3, 0, 6, 1); + auto res = sched.scheduleForPP(); + + // Sorted by (arrival, priority, PID): P3(pri1)=idx0, P1(pri2)=idx1, P2(pri3)=idx2 + assert(get<4>(res[0]) == 6.0); // P3 CT + assert(get<6>(res[0]) == 0.0); // P3 WT + + assert(get<4>(res[1]) == 13.0); // P1 CT + assert(get<6>(res[1]) == 6.0); // P1 WT + + assert(get<4>(res[2]) == 17.0); // P2 CT + assert(get<6>(res[2]) == 13.0); // P2 WT + } + + // ------------------------------------------------------------------- + // Test 6: Preemptive, single process — no waiting, no preemption + // ------------------------------------------------------------------- + { + PreemptivePriority sched; + sched.addProcess(5, 3, 8, 1); + auto res = sched.scheduleForPP(); + + assert(res.size() == 1); + assert(get<4>(res[0]) == 11.0); // CT = 3 + 8 + assert(get<5>(res[0]) == 8.0); // TAT = 8 + assert(get<6>(res[0]) == 0.0); // WT = 0 + } + + cout << "All tests have successfully passed!" << endl; +} + +/** + * @brief Entry point of the program + * @returns 0 on exit + */ +int main() { + test(); // run self-test implementations + + // ------------------------------------------------------------------- + // Demo 1: Non-preemptive Priority Scheduling (GFG example) + // P1(arr=0,burst=4,pri=2), P2(arr=1,burst=2,pri=1), P3(arr=2,burst=6,pri=3) + // ------------------------------------------------------------------- + cout << "\n--- Non-Preemptive Priority Scheduling Demo ---\n"; + cpu_scheduling_algorithms::priority_scheduling::NonPreemptivePriority< + uint32_t, uint32_t, uint32_t, uint32_t> + npp; + npp.addProcess(1, 0, 4, 2); + npp.addProcess(2, 1, 2, 1); + npp.addProcess(3, 2, 6, 3); + npp.scheduleForNPP(); + npp.printResult(); + + // ------------------------------------------------------------------- + // Demo 2: Preemptive Priority Scheduling (different arrival times) + // P1(arr=0,burst=6,pri=2), P2(arr=1,burst=4,pri=1), P3(arr=2,burst=5,pri=3) + // ------------------------------------------------------------------- + cout << "\n--- Preemptive Priority Scheduling Demo ---\n"; + cpu_scheduling_algorithms::priority_scheduling::PreemptivePriority< + uint32_t, uint32_t, uint32_t, uint32_t> + pp; + pp.addProcess(1, 0, 6, 2); + pp.addProcess(2, 1, 4, 1); + pp.addProcess(3, 2, 5, 3); + pp.scheduleForPP(); + pp.printResult(); + + return 0; +}