← Back to 25T3

Assignment 1 - Timetable Manager



Changelog

Check this section for updates to the assignment spec

Background

You have been tasked with creating a new system to support the management of classes at a university. The system should allow for the management of classes, students, and class enrolments. We also want to be able to identify schedule conflicts and available classes for students, as well as support waitlists for classes that are full.

Learning Outcomes

The purpose of this assignment is to consolidate the C++ concepts taught in weeks 1-4. The main learning outcomes are:

Summary

Marks

contributes 20% of your final mark

Due Date

Week 7 Monday, 10:00am

Late Penalty

5% per day, submissions later than 5 days not accepted without approved special consideration

Prerequisite Knowledge

Setting up…

Ensure you have followed the VSCode/C++ and git guides on EdStem to set up your environment.

To check that your environment is set up correctly, you should be able to run the following commands in the terminal:

cmake --version # should be 3.28 or higher
make --version
g++ --version
git --version

Getting Started

Retrieving the starter code

To get the assignment starter code, use the GitHub classroom link on Moodle to gain access to your assignment repository and clone the repo locally. (You can use the git clone command in the terminal).

If you have any questions/issues, ask your lab demonstrator.

Examining the starter code

Have a look at the following files in the starter code:

FileDescription
include/timetable_manager.hppHeader file for the TimetableManager class. You will need to submit this file.
src/timetable_manager.cppImplementation file for the TimetableManager class. You will need to submit this file.
src/main.cppA main file that you can modify to manually test your code. You won’t need to submit this file.
test/timetable_manager_test.cppA test file that contains unit tests for the TimetableManager class. You are strongly encouraged to add additional tests to check the correctness of your code. You won’t need to submit this file.
include/types.hppA header file containing type definitions. DO NOT MODIFY THIS FILE. You won’t need to submit this file.

A deeper dive into the include/types.hpp file

The include/types.hpp file contains the following type definitions:

  1. An enum class Day that represents the days of the week. The days are ordered from Monday to Sunday.
enum class Day { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };
  1. A struct Time that represents a time of the week. The struct has three members: day, hour and minute. You may assume that the hour and minute values are always valid (i.e. hour is between 0 and 23, and minute is between 0 and 59).
struct Time { Day day; int hour; // 0 to 23 int minute; // 0 to 59 };
  1. A struct TimeSpan that represents a time span. The struct has two members: start and end, both of type Time. The time range is inclusive of the start time and exclusive of the end time. The time span can span across days (e.g., from Friday 23:00 to Saturday 01:00) and can wrap around the week (e.g., from Sunday 22:00 to Monday 02:00). You may assume that no time-span lasts longer than a week.
struct TimeSpan { // a time span across [start, end) Time start; Time end; };
  1. A struct Class that represents a class. The struct has four members: id, capacity, instructor, and timeSpan. The id is a std::string that uniquely identifies the class. The capacity is an integer that represents the maximum number of students that can enrol in the class. The instructor is a std::string that represents the instructor of the class. The timeSpan is a TimeSpan that represents the time span of the class.
struct Class { std::string id; int capacity; std::string instructor; TimeSpan time; };
  1. [STAGE 4 ONLY] A struct ClassSwap that represents when a student moves off a waitlist to become enrolled in a class. The struct has three members: studentId, oldClassId, and newClassId. The studentId is a std::string that uniquely identifies the student. The oldClassId is a std::optional<std::string> that represents the class the student was previously enrolled in (or std::nullopt if this is not a transfer). The newClassId is a std::string that represents the class the student is now enrolled in.
struct ClassSwap { std::string studentId; std::optional<std::string> oldClassId; std::string newClassId; };

Compiling, running and testing your code

Open up a terminal and use the cd command to change into your assignment directory.

You have been provided a script to compile your code: build.sh. You can run this script by typing the following into the terminal:

./build.sh

This script will compile your code and create two executable files in the build directory: PrimaryMain and TestSuite.


You can run the PrimaryMain executable to run your code in the src/main.cpp file:

./build/PrimaryMain

You can run the TestSuite executable to run the test suite in the test/timetable_manager_test.cpp file:

./build/TestSuite

You should see most of the tests fail (obviously, since we haven’t implemented anything yet!)

Task

The following section details the requirements for each method in the TimetableManager class that you must implement. The method type signature, description and an example usage in the style of a Catch2 test has been provided.

Stage 1

In this stage, we will implement functionality in the TimetableManager class to manage classes. We should be able to add / remove classes and get information about the classes in the timetable manager.

TimetableManager();

A constructor for the TimetableManager class that initialises a timetable manager with no classes.

TimetableManager(const std::vector<Class>& classes);

A constructor for the TimetableManager class that initialises a timetable manager with the classes in the vector classes.

Throws a std::invalid_argument exception if any classes in the vector have the same id.

~TimetableManager();

A destructor for the TimetableManager class. You should ensure that any allocated memory is correctly freed.

void addClass(const Class& c);

Adds a class c to the timetable manager.

If a class with the same id already exists, does not add the class and throws a std::invalid_argument exception.

void addClass(const std::string& id, int capacity, const std::string& instructor, Time startTime, Time endTime);

Adds a class to the timetable manager, with id id, capacity capacity, instructor instructor, and a time spanning from startTime to endTime.

If a class with the same id already exists, does not add the class and throws a std::invalid_argument exception.

void removeClass(const std::string& id);

Removes a class from the timetable manager with id id.

If no class with the given id exists, does not remove any classes and throws a std::invalid_argument exception.

void setClassCapacity(const std::string& id, int capacity);

Sets the capacity of the class with id id to capacity.

Throws a std::invalid_argument exception if no class with the given id exists or if capacity is not a positive integer.

[STAGE 2] Throws a std::logic_error if the capacity is less than the number of students enrolled in the class.

void setClassInstructor(const std::string& id, const std::string& instructor);

Sets the instructor of the class with id id to instructor.

Throws a std::invalid_argument exception if no class with the given id exists.

int getNumClasses() const;

Returns the number of classes in the timetable manager.

int getClassCapacity(const std::string& id) const;

Returns the capacity of the class with id id.

Throws a std::invalid_argument exception if no class with the given id exists.

std::string getClassInstructor(const std::string& id) const;

Returns the instructor of the class with id id.

Throws a std::invalid_argument exception if no class with the given id exists.

std::vector<std::string> getClassIds() const;

Returns a vector of all ids of classes in the timetable manager, sorted lexicographically.

std::string getEarliestClass() const;

Returns the id of the class with the earliest start time of the day (i.e., ignoring the day of the week and only considering the time of day). If there are multiple classes with the same earliest start time, return the id of the class that comes first lexicographically.

Throws a std::logic_error exception if there are no classes in the timetable manager.

std::string getLatestClass() const;

Returns the id of the class with the latest start time of the day (i.e., ignoring the day of the week and only considering the time of day). If there are multiple classes with the same latest start time, return the id of the class that comes first lexicographically.

Throws a std::logic_error exception if there are no classes in the timetable manager.

std::string getLongestClass() const;

Returns the id of the class with the longest duration. If there are multiple classes with the same longest duration, return the id of the class that comes first lexicographically.

Throws a std::logic_error exception if there are no classes in the timetable manager.

std::vector<std::string> getClassesAtTime(Time time) const;

Returns a vector of ids of all classes that are running at the given time time, sorted lexicographically.

void printClasses(std::ostream& os) const;

Prints information about all classes in the timetable manager to the output stream os. The classes should be ordered lexicographically by id. Each class should be printed on a new line in the format Class <id> has capacity <capacity> and is taught by <instructor>.

Stage 2

In this stage we will add functionality to enrol students in classes. We will identify students by their student id (a std::string).

void enrolStudent(const std::string& classId, const std::string& studentId);

Enrols a student with id studentId in the class with id classId.

If the class with the given id does not exist, throws a std::invalid_argument exception. If the class is full (i.e. the number of students enrolled is equal to the capacity), does not enrol the student and throws a std::logic_error exception.

Does nothing if the student is already enrolled in the class.

void unenrolStudent(const std::string& classId, const std::string& studentId);

Unenrols a student with id studentId from the class with id classId.

If the student with the given id is not enrolled in the class, or the classId is invalid, does not unenrol the student and throws a std::invalid_argument exception.

int getNumStudents() const;

Returns the number of students enrolled in any class.

int getNumEnrollments(const std::string& classId) const;

Returns the number of students enrolled in the class with id classId.

Throws a std::invalid_argument exception if the class with the given id does not exist.

std::vector<std::string> getEnrolledStudents(const std::string& classId) const;

Returns a vector of student ids enrolled in the class with id classId. The ids should be returned in lexicographical order.

Throws a std::invalid_argument exception if the class with the given id does not exist.

std::vector<std::string> getEnrolledClasses(const std::string& studentId) const;

Returns a vector of class ids that the student with id studentId is enrolled in. The ids should be returned in lexicographical order.

std::vector<std::string> getAllStudents() const;

Returns a vector of all student ids enrolled in any class, ordered lexicographically.

std::vector<std::string> getAllStudents(const std::string& instructor) const;

Returns a vector of all student ids enrolled in any class taught by the instructor instructor, ordered lexicographically.

TimetableManager& operator+=(const Class& c);

Adds a class c to the timetable manager.

If a class with the same id already exists, does not add the class and throws a std::invalid_argument exception.

TimetableManager& operator-=(const std::string& id);

Removes a class with id id from the timetable manager.

If no class with the given id exists, does not remove any classes and throws a std::invalid_argument exception.

TimetableManager& operator+=(const TimetableManager& other);

Adds all classes and enrollments from the timetable manager other to the current timetable manager. Does NOT add classes (and enrollments to classes) with the same id.

TimetableManager operator+(const TimetableManager& other);

Returns a new timetable manager that contains all classes and enrollments from the current timetable manager and the timetable manager other. If both timetable managers contain classes with the same id, the class from the current timetable manager should be included.

TimetableManager(const TimetableManager& other);

A copy constructor for the TimetableManager class. Creates a new timetable manager that is a copy of the timetable manager other but with no enrollments.

TimetableManager(TimetableManager&& other);

A move constructor for the TimetableManager class. Moves the contents of the timetable manager other to the new timetable manager.

TimetableManager& operator=(const TimetableManager& other);

A copy assignment operator for the TimetableManager class. Creates a new timetable manager that is a copy of the timetable manager other but with no enrollments. If the current timetable manager already has classes, they should be cleared before copying the classes from other.

TimetableManager& operator=(TimetableManager&& other);

A move assignment operator for the TimetableManager class. Moves the contents of the timetable manager other to the current timetable manager. If the current timetable manager already has classes, they should be cleared before moving the classes from other.

Stage 3

In this stage, we will add more operators to the TimetableManager class to compare timetable managers and get information about schedule conflicts and available classes.

bool operator==(const TimetableManager& other) const;

Returns true if the timetable manager is equal to the timetable manager other. Two timetable managers are equal if they contain the same classes and enrollments.

Classes are considered equal if they have the same id, capacity, instructor, and time range. Enrollments are considered equal if the same students are enrolled in the same classes.

bool operator!=(const TimetableManager& other) const;

Returns true if the timetable manager is not equal to the timetable manager other.

Two timetable managers are not equal if they contain different classes or enrollments.

bool operator<(const TimetableManager& other) const;

Returns true if the total duration of all classes in the timetable manager is strictly less than the total duration of all classes in the timetable manager other.

bool operator>(const TimetableManager& other) const;

Returns true if the total duration of all classes in the timetable manager is strictly greater than the total duration of all classes in the timetable manager other.

std::vector<std::pair<std::string, std::string>> getScheduleConflicts( const std::string& studentId) const;

Returns a vector of pairs of class ids that the student with id studentId is enrolled in that have overlapping times. You can return the pairs in any order.

std::vector<std::string> getAvailableClasses( const std::string& studentId) const;

Returns a vector of class ids that the student with id studentId is not enrolled in and that do not have overlapping times with classes that the student is enrolled in.

std::vector<TimeSpan> getFreeTimes( const std::set<std::string>& studentIds, Time start, Time end) const;

Returns a vector of time spans that are free for all students in the set studentIds between the times start and end. A time span is free if no students have classes that are running at that time. The time spans should be ordered by start time.

You may assume that start will be earlier (in the week) than end.

Stage 4 (Advanced Task)

In this stage, we will add functionality to allow students to waitlist for classes and request a class swap, via the enrolOrWaitlistStudent and transferOrWaitlistStudent methods.

If a class is full, instead of throwing an error, the student is added to a ‘waitlist’ for the class. When a spot becomes available (e.g. another student unenrols or transfers out of the class), the next student on the waitlist should be automatically enrolled in the class.

You may additionally need to extend the functionality of previous methods to handle these new features.

The timetable manager should also notify a handler function whenever a student is moved from a waitlist to an enrolled list. The setClassSwapHandler method can be used to set a handler function that will be called whenever a student is moved from a waitlist to an enrolled list. The handler function should take a vector of ClassSwap objects as an argument. The TimetableManager class should call this handler function whenever a student is moved from a waitlist to an enrolled list.

An example of how this “callback pattern” can be used is shown here:

TimetableManager manager{}; // set the handler to a callback that prints class swaps to stdout (std::cout) manager.setClassSwapHandler([](const std::vector<ClassSwap>& swaps) { for (auto& s : swaps) { std::cout << s.studentId << " has successfully enrolled in " << s.newClassId << "\n"; } }); // add a class with capacity 1 manager.addClass("MTRN2500", 1, "John Doe", {Day::Monday, 9, 0}, {Day::Monday, 10, 0}); // enrol a student manager.enrolOrWaitlistStudent("MTRN2500", "z0000000"); // try to enrol another student (but class is full, so they get "waitlisted") manager.enrolOrWaitlistStudent("MTRN2500", "z1111111"); // first student unenrols -> we expect the TimetableManager to call the handler // with a vector {{"MTRN2500", std::nullopt, "z1111111"}} since z1111111 should // be moved off the waitlist for MTRN2500 manager.unenrolStudent("MTRN2500", "z0000000"); // handler called

Output to stdout:

z1111111 has successfully enrolled in MTRN2500
void setClassSwapHandler( std::function<void(const std::vector<ClassSwap>&)> handler);

Sets a handler function that will be called whenever a student is moved from a waitlist to an enrolled list. The handler function should take a vector of ClassSwap objects as an argument.

void enrolOrWaitlistStudent(const std::string& classId, const std::string& studentId);

Enrols a student with id studentId in the class with id classId. If the class is full, the student should be added to a ‘waitlist’.

If the class with the given id does not exist, throws a std::invalid_argument exception.

void transferOrWaitlistStudent(const std::string& studentId, const std::string& oldClassId, const std::string& newClassId);

Requests to transfer a student with id studentId from the class with id oldClassId to the class with id newClassId.

If the class with id newClassId is full, the student should be added to a ‘waitlist’ for that class. If the class is not full, the student should be moved from oldClassId to newClassId.

If the class with the given id (either oldClassId or newClassId) does not exist, throws a std::invalid_argument exception. If the student is not enrolled in the class with id oldClassId, or is already enrolled in newClassId, does nothing.

Stage 4 Special Cases

Behvaiour for various complex cases regarding waitlists and transfers are listed here. If any cases are not covered, feel free to post on EdStem in the clarifications thread.

Transferring student unenrols from old class

If a student requests to transfer from class A to class B, and then unenrols from class A before the transfer is completed, if the student eventually gets a spot in class B, they should be enrolled in class B, with the oldClassId field of the ClassSwap object set to std::nullopt (i.e., they are not transferring from any class).

TimetableManager manager{}; manager.setClassSwapHandler([](const std::vector<ClassSwap>& swaps) { // ... }); // classes with capacity 1 each manager.addClass("MTRN2500", 1, "John Doe", {Day::Monday, 9, 0}, {Day::Monday, 10, 0}); manager.addClass("MTRN3500", 1, "Jane Doe", {Day::Tuesday, 10, 0}, {Day::Tuesday, 11, 0}); // students are enrolled in MTRN2500 and MTRN3500, making both classes full manager.enrolOrWaitlistStudent("MTRN2500", "z0000000"); manager.enrolOrWaitlistStudent("MTRN3500", "z1111111"); // student requests to transfer to MTRN3500 manager.transferOrWaitlistStudent("z0000000", "MTRN2500", "MTRN3500"); // student unenrols from MTRN2500 before the transfer is completed manager.unenrolStudent("MTRN2500", "z0000000"); // student unenrols from MTRN3500, creating a spot manager.unenrolStudent("MTRN3500", "z1111111"); // handler called with {{"z0000000", std::nullopt, "MTRN3500"}}

Resolving Waitlist Deadlocks

This case is quite difficult and is meant as a fun challenge for top students. It will be worth very few marks.

Suppose two students are enrolled in two full classes, and both students request to transfer to the other class. For example:

TimetableManager manager{}; // classes with capacity 1 each manager.addClass("MTRN2500", 1, "John Doe", {Day::Monday, 9, 0}, {Day::Monday, 10, 0}); manager.addClass("MTRN3500", 1, "Jane Doe", {Day::Tuesday, 10, 0}, {Day::Tuesday, 11, 0}); // both students are enrolled in their respective classes manager.enrolOrWaitlistStudent("MTRN2500", "z0000000"); manager.enrolOrWaitlistStudent("MTRN3500", "z1111111"); // both students request to transfer to the other class manager.transferOrWaitlistStudent("z0000000", "MTRN2500", "MTRN3500"); manager.transferOrWaitlistStudent("z1111111", "MTRN3500", "MTRN2500");

In this case, the TimetableManager should resolve the deadlock by moving both students to the other class, effectively swapping them. The class swap handler function (if set) should be called appropriately as well.

REQUIRE(manager.getNumEnrollments("MTRN2500") == 1); REQUIRE(manager.getNumEnrollments("MTRN3500") == 1); // After the transfers, z0000000 should be in MTRN3500 and z1111111 should be in MTRN2500 REQUIRE(manager.getEnrolledStudents("MTRN2500") == std::vector<std::string>{"z1111111"}); REQUIRE(manager.getEnrolledStudents("MTRN3500") == std::vector<std::string>{"z0000000"});

This scenario could be extended to more than two students and classes, but the basic principle remains the same.

Any time it is possible to resolve a deadlock by swapping students between classes, the TimetableManager should do so. If there are multiple possible ways to resolve the deadlock, the TimetableManager can choose any of them.

Other Requirements

Using libraries

You may #include any standard C++ libraries that you require. You should not use any other libraries (note that you only submit the TimetableManager.cpp and TimetableManager.hpp files).

Move Semantics

After an object has been moved (using the move constructor) it must be invalidated. We define invalidation of the TimetableManager class as containing no classes or enrollments. Once moved, the original object should no longer be used and any other behaviour is undefined.

Throwing Exceptions

For several of the methods in the TimetableManager class, you will need to throw exceptions. When throwing exceptions, you should throw the appropriate exception type and include a meaningful error message. The autotests will check that you are throwing the correct exception type and will ignore the error message.

e.g. For the addClass method, you should throw a std::invalid_argument exception if a class with the same id already exists. This could be implemented as

throw std::invalid_argument("A class with the same id already exists");

Hard Coding

Hard coding will not be accepted for any of the functions. Any function that is found to be implemented via hard coding will receive a result of zero.

An example of hard coding would be:

// This code does not actually compute the longest class, it just returns a hard-coded value // based on the number of classes (probably to pass as many tests as possible) std::string TimetableManager::getLongestClass() const { if (numClasses == 0) { return ""; } else if (numClasses == 1) { return "MTRN2500"; } else if (numClasses == 2) { return "MTRN3500"; } return "MTRN4500"; }

C++ Version

This assignment should be written with modern C++ principles (see the style guide). The starter code and environment supplied is running C++17. You should be writing all code that is supported by this version of C++.

Version Control

As part of this assignment, you will be required to use GitHub classroom. A repository has been established for all students and the starter code has been placed within this (it will NOT be released on Moodle). You will be expected to meaningfully use this version control platform as it is widely used in industry and provides a means of retrieving code in case of computer issues or getting old revisions of your work.

Testing

You have been provided with a testing suite that you can use to sanity-check your code. This is not exhaustive and you are strongly encouraged to write your own tests to ensure your code is correct. You can do this by adding to the given test suite.

The tests have been written using the Catch2 framework.

Your “correctness” marks for this assignment will be based on a combination of the provided tests and additional tests that we will run on your code.

Marking Criteria

The assignment will be marked out of 20 marks. The “correctness” of your assignment will be automatically marked based on the provided tests and additional tests that we will run on your code. The “style” of your code will be manually marked according to the style rubric below.

ComponentWeighting
Correctness - Stage 14 marks
Correctness - Stage 25 marks
Correctness - Stage 33 marks
Correctness - Stage 43 marks
Style5 marks

Style Rubric

Please refer to the C++ Style Guide for the detailed style guide. Your code style will be manually marked according to the following criteria:

CriteriaMarksDescription
function use0.8effective use of functions to avoid repeated code, no overly deep nesting
commenting0.8effective use of comments to describe purpose of code, helper functions have comments explaining the purpose of the function
naming style0.4consistent naming style (camelCase OR snake_case)
descriptive function + variable names0.4all function and variables names are descriptive and appropriate
passing by reference0.2non-primitive types are consistently passed by reference
move semantics0.4correct application of move semantics (see “Other Requirements > Move Semantics” section above)
class access specifiers and const correctness0.2effective use of public and private specifiers for abstraction, defining helper functions as const wherever possible
spacing0.8consistent spacing, no whitespace errors
C++ style0.4effective use of C++ features (e.g. std::array, new, delete, constexpr, const, static_cast, nullptr)
loops and algorithms0.6effective use of in-built C++ STL algorithms where appropriate and good loop choices
penalties (negative marks applied)-failing to avoid using using namespace, void*, #define

In order to be eligible to receive full marks for style, you must make a reasonable attempt at Stage 1 and 2. If you only attempt Stage 1, your style mark will be capped at 3/5.

Submission

The assignment is due by 10:00am on Monday Week 7 (27 Oct 2025). It should be submitted via the submission box provided on Moodle under the “Assessments Hub” section. You will need to submit 2 files: timetable_manager.cpp and timetable_manager.hpp.

You must also ensure that you have pushed the final version of your assignment to GitHub.

Compiling code

You must ensure that your code will compile by adhering to the public interface specified in the assignment spec. Only the functions listed in the spec (above) will be called by the autotests. Please do not delete the function declarations or change the function signatures, as your code will not compile with the tests.

Submitting non-compiling code will lead to a 10% penalty.

Late Penalty

UNSW has a standard late submission penalty of: 5% per day, for all assessments where a penalty applies, capped at five days (120 hours) from the assessment deadline, after which a student cannot submit an assessment.

Plagiarism

If you are unclear about the definition of plagiarism, please refer to What is Plagiarism? | UNSW Current Students.

You could get zero marks for the assignment if you were found:

You will be notified and allowed to justify your case before such a penalty is applied.

AI Policy

You are only permitted to use AI to the level of “simple editing assistance” (defined here) for this assignment. You should NOT use AI to generate code for you. Using functionality that generates code can be grounds for academic misconduct.