This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
university-timetable/main.cpp
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
349 lines (319 sloc)
16 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <cstdio> | |
#include <bits/stdc++.h> //needed for associative array implementation | |
#include <sqlite3.h> //using sqlite3 library for database integration | |
//small helper functions | |
int lengthCArray( char** CArray ) | |
{ | |
return sizeof CArray / sizeof *CArray ; | |
} | |
int lengthCArray( int** CArray ) | |
{ | |
return sizeof CArray / sizeof *CArray ; | |
} | |
//main program functions | |
static int displayRecordsCallback(void* data, int numColumns, char** columnValue, char** columnName) | |
{ | |
std::cout << std::endl; | |
std::cerr << (char*)data << std::endl; //echo any additional custom arguments passed in the callback | |
for (int i = 0; i < numColumns; i++) { | |
std::cout << columnName[i] << "\t" << columnValue[i] << "\t"; | |
} | |
return 0; | |
} | |
static int displayFormattedClassesCallback(void* data, int numColumns, char** columnValue, char** columnName) | |
{ | |
int time_24hrs, length_hrs; | |
std::string room_id, module_code; | |
std::string class_type; | |
std::cout << std::endl; | |
std::cerr << (char*)data << std::endl; //echo any additional custom arguments passed in the callback | |
//store all record values in their appropriate variable to "cache" them for easier later use inside this function | |
for (int i = 0; i < numColumns; i++) { | |
if (columnName[i] == "time_24hrs") { | |
time_24hrs = (int)columnValue[i]; | |
} | |
else if (columnName[i] == "length_hrs") { | |
length_hrs = (int)columnValue[i]; | |
} | |
else if (columnName[i] == "room_id") { | |
room_id = columnValue[i]; | |
} | |
else if (columnName[i] == "module_code") { | |
module_code = columnValue[i]; | |
} | |
else if (columnName[i] == "class_type") { | |
class_type = columnValue[i]; | |
} | |
} | |
//records should be ordered by time ascending | |
//always print time first | |
std::cout << time_24hrs << "\t+-- "; | |
//print room and module code second on the first line of the timetabled class | |
std::cout << module_code << "\t" << room_id; | |
std::cout << std::endl << "\t| "; //start a new line for the second line of this timetabled class | |
//print lecturer and class type on the second line | |
std::cout << class_type << "\t"; | |
//continue printing a vertical line until the end of the timetabled class | |
for (int i = length_hrs-1; i > 0; i--) { | |
std::cout << std::endl << time_24hrs + (length_hrs - i) << "\t| "; | |
std::cout << std::endl << "\t| "; | |
} | |
return 0; | |
} | |
static int generateTimetableModulesCallback(char** moduleCodes, int numColumns, char** columnValue, char**columnName) | |
{ | |
for (int i = 0; i < lengthCArray(moduleCodes); i++) { | |
std::cerr << moduleCodes[i] << std::endl; | |
if (moduleCodes[i] == "") { | |
moduleCodes[i] = columnValue[0]; //we effectively populate the array declared in the calling scope with values from the database | |
std::cerr << moduleCodes[i] << std::endl; | |
break; | |
} | |
} | |
return 0; | |
} | |
static int generateTimetableRoomsCallback(char** roomIds, int numColumns, char** columnValue, char** columnName) | |
{ | |
for (int i = 0; i < lengthCArray(roomIds); i++) { | |
std::cerr << roomIds[i] << std::endl; | |
if (roomIds[i] == "") { | |
roomIds[i] = columnValue[0]; //we effectively populate the array declared in the calling scope with values from the database | |
std::cerr << roomIds[i] << std::endl; | |
break; | |
} | |
} | |
return 0; | |
} | |
static int generateTimetableLecturersCallback(char** lecturerIds, int numColumns, char** columnValue, char** columnName) | |
{ | |
for (int i = 0; i < lengthCArray(lecturerIds); i++) { | |
std::cerr << lecturerIds[i] << std::endl; | |
if (lecturerIds[i] == "") { | |
lecturerIds[i] = columnValue[0]; //we effectively populate the array declared in the calling scope with values from the database | |
std::cerr << lecturerIds[i] << std::endl; | |
break; | |
} | |
} | |
return 0; | |
} | |
int displayTable( sqlite3* db, int exit, char* sqlErrorMessage ) | |
{ | |
std::string tableName; | |
char query[100]; //use C string so it can be formatted using snprintf | |
std::cout << "Enter table name: "; | |
std::cin >> tableName; | |
snprintf(query, 100, "SELECT * FROM %s ;", tableName.c_str()); | |
//execute sql commands | |
//sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg) | |
//arguments taken from https://www.geeksforgeeks.org/sql-using-c-c-and-sqlite/ | |
exit = sqlite3_exec(db, query, displayRecordsCallback, NULL, &sqlErrorMessage ); | |
//report back to user if execution was successful, and if not why | |
if (exit != SQLITE_OK) { | |
std::cerr << "Some errors occured during select:" << std::endl; | |
std::cerr << sqlErrorMessage << std::endl; | |
} | |
std::cout << std::endl; | |
std::cout << "Done." << std::endl; | |
return 0; | |
} | |
int displayTimetable( sqlite3* db, int exit, char* sqlErrorMessage ) | |
{ | |
std::string query = "SELECT * FROM Classes ORDER BY time_24hrs ASC ;"; | |
//execute sql commands | |
//sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg) | |
//arguments taken from https://www.geeksforgeeks.org/sql-using-c-c-and-sqlite/ | |
exit = sqlite3_exec(db, query.c_str(), displayFormattedClassesCallback, NULL, &sqlErrorMessage ); | |
//report back to user if execution was successful, and if not why | |
if (exit != SQLITE_OK) { | |
std::cerr << "Some errors occured during select:" << std::endl; | |
std::cerr << sqlErrorMessage << std::endl; | |
} | |
std::cout << std::endl; | |
std::cout << "Done." << std::endl; | |
return 0; | |
} | |
int generateTimetable( sqlite3* db, int exit, char* sqlErrorMessage ) | |
{ | |
/* the main outer loop for the algorithm is going to be the list of modules | |
we are going to loop through each module and assign classes as we go | |
until no more will fit | |
since this is an overview timetable (as opposed to being for a specific person) | |
multiple classes can be assigned at the same time as long as they don't use the same room or lecturer etc. | |
disadvantage to approach: bias will be created towards allocation of earlier modules over later modules | |
this can be modified to our liking by changing the order returned in the sql query | |
*/ | |
int lengthHrsPerClass; | |
int startAllocationTime; | |
int endByTime; | |
int classesPerModule; | |
char* moduleCodes[20]; //use pointer arrays so these can be remotely populated from the SQL query callback functions | |
char* roomIds[20]; | |
char* lecturerIds[20]; | |
//initially fill the arrays with dummy values so we can identify unallocated elements | |
for (int i = 0; i < lengthCArray(moduleCodes); i++) { | |
moduleCodes[i] = ""; roomIds[i] = ""; lecturerIds[i] = ""; | |
} | |
//get some user input to affect how we generate the timetable | |
std::cout << "Enter the desired length of each class (in hours) (must be a whole number):" << std::endl; | |
std::cin >> lengthHrsPerClass; | |
std::cout << "Enter the earliest class allocation time (in 24 hours) (must be whole number):" << std::endl; | |
std::cin >> startAllocationTime; | |
std::cout << "Enter the latest time a class can run until (in 24 hours) (must be whole number):" << std::endl; | |
std::cin >> endByTime; | |
std::cout << "Enter the desired number of classes per module:" << std::endl; | |
std::cin >> classesPerModule; | |
//execute sql commands | |
//sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg) | |
//arguments taken from https://www.geeksforgeeks.org/sql-using-c-c-and-sqlite/ | |
exit = sqlite3_exec(db, "SELECT module_code FROM Modules ;", generateTimetableModulesCallback, moduleCodes, &sqlErrorMessage ); | |
exit = sqlite3_exec(db, "SELECT room_id FROM Rooms ;", generateTimetableRoomsCallback, roomIds, &sqlErrorMessage ); | |
exit = sqlite3_exec(db, "SELECT lecturer_id FROM Lecturers ;", generateTimetableLecturersCallback, lecturerIds, &sqlErrorMessage ); | |
//report back to user if execution was successful, and if not why | |
if (exit != SQLITE_OK) { | |
std::cerr << "Some errors occured during select:" << std::endl; | |
std::cerr << sqlErrorMessage << std::endl; | |
return 1; | |
} | |
//associative array using map type definition achieved with help from example code at https://www.geeksforgeeks.org/associative-arrays-in-cpp/ | |
std::vector< std::map<std::string, std::string> > allocatedClasses; | |
int tempClassId = 11; //this is incremented after a successful allocation | |
std::string tempRoomId; | |
std::string tempLecturerId; | |
bool allocationSuccessful; | |
char query[250]; //use C string so it can be formatted using snprintf | |
int numClassesSuccessful = 0; | |
int numClassesFailed = 0; | |
//start allocating timetable classes, outer loop iterating through module codes | |
for (int i = 0; i < lengthCArray(moduleCodes); i++) { | |
if (moduleCodes[i] != "") { | |
for (int moduleCycle = 0; moduleCycle < classesPerModule; moduleCycle++) { | |
//attempt to allocate timetabled class | |
tempRoomId = ""; | |
tempLecturerId = ""; | |
allocationSuccessful = false; | |
for (int tempTime = startAllocationTime; tempTime <= endByTime - lengthHrsPerClass; tempTime++) { | |
//iterate through room indexes | |
for (int ri = 0; ri < lengthCArray(roomIds); ri++) { | |
tempRoomId = roomIds[ri]; | |
//iterate through allocated classes indexes | |
for (int ci = 0; ci < allocatedClasses.size(); ci++) { | |
//check if times overlap, and if they do cancel out this room allocation | |
if ((int)allocatedClasses[ci]["time_24hrs"].c_str() + lengthHrsPerClass > tempTime | |
&& (int)allocatedClasses[ci]["time_24hrs"].c_str() < tempTime + lengthHrsPerClass | |
&& allocatedClasses[ci]["room_id"] == roomIds[ri]) { | |
tempRoomId = ""; | |
} | |
} | |
if (tempRoomId != "") | |
break; //a free room has been selected | |
} | |
//iterate through lecturer indexes | |
for (int li = 0; li < lengthCArray(lecturerIds); li++) { | |
tempLecturerId = lecturerIds[li]; | |
//iterate through allocated classes indexes | |
for (int ci = 0; ci < allocatedClasses.size(); ci++) { | |
//check if times overlap, if so cancel out this lecturer allocation | |
if ((int)allocatedClasses[ci]["time_24hrs"].c_str() + lengthHrsPerClass > tempTime | |
&& (int)allocatedClasses[ci]["time_24hrs"].c_str() < tempTime + lengthHrsPerClass | |
&& allocatedClasses[ci]["lecturer_id"] == lecturerIds[li]) { | |
tempLecturerId = ""; | |
} | |
} | |
if (tempLecturerId != "") | |
break; //a free lecturer has been selected | |
} | |
if (tempRoomId != "" && tempLecturerId != "") { | |
//we have a valid time for our class! | |
//execute the sql first, so if for whatever reason an error occurs, our tracked allocated classes isn't updated | |
std::cerr << "Valid allocation found! Attempting to insert into database." << std::endl; | |
snprintf(query, 250, "INSERT INTO Classes VALUES ( %d , \"%s\" , %s , \"%s\" , %s , %d , %d ) ;", tempClassId, tempRoomId.c_str(), tempLecturerId.c_str(), | |
moduleCodes[i], "NULL", tempTime, lengthHrsPerClass); | |
//execute sql commands | |
//sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void *data, char **errmsg) | |
//arguments taken from https://www.geeksforgeeks.org/sql-using-c-c-and-sqlite/ | |
exit = sqlite3_exec(db, query, NULL, NULL, &sqlErrorMessage ); | |
//report back to user if execution was successful, and if not why | |
//if it fails, loop back around and try the next time like always | |
if (exit != SQLITE_OK) { | |
std::cerr << "A valid allocation was found, but some errors occured during insert into database:" << std::endl; | |
std::cerr << sqlErrorMessage << std::endl; | |
std::cerr << "A new valid allocation will attempt to be found." << std::endl; | |
} | |
else { | |
//then append to keep track of the new class, and break out of this class allocation loop | |
std::cerr << "Insert successful. Attempting to append to tracked." << std::endl; | |
allocatedClasses.push_back( { {"class_id", (char*)&tempClassId}, {"room_id", tempRoomId}, {"lecturer_id", tempLecturerId}, | |
{"module_code", moduleCodes[i]}, {"class_type", "NULL"}, {"time_24hrs", (char*)&tempTime}, {"length_hrs", (char*)&lengthHrsPerClass} } ); | |
tempClassId++; | |
numClassesSuccessful++; | |
allocationSuccessful = true; | |
std::cout << "Appended to tracked." << std::endl; | |
break; | |
} | |
} | |
} | |
//if the program natually reached here instead of breaking out of the loop, then no valid class allocation could be found | |
if (!allocationSuccessful) { | |
numClassesFailed++; | |
std::cout << "A class could not be allocated on the timetable. Module: " << moduleCodes[i] << " cycle number: " << moduleCycle << std::endl; | |
} | |
} | |
} | |
} | |
//end of timetable classes allocation algorithm | |
//display statistics | |
std::cout << std::endl; | |
std::cout << "Timetable generation complete! Statistics:" << std::endl; | |
std::cout << "\ttotal Classes Attempted: " << numClassesSuccessful + numClassesFailed << "\tnum Classes Successful: " << numClassesSuccessful | |
<< "\tnum Classes Failed: " << numClassesFailed << std::endl; | |
return 0; | |
} | |
int main() | |
{ | |
sqlite3* db; | |
int exit = 0; | |
char* sqlErrorMessage; | |
int menuChoice; | |
//open connection to database | |
exit = sqlite3_open("data.db", &db); //will return 0 if succeeded | |
//check connection was successful before continuing | |
//example code taken from https://www.geeksforgeeks.org/sql-using-c-c-and-sqlite/ | |
if (exit) { | |
std::cerr << "Error opening database: " << sqlite3_errmsg(db) << std::endl; | |
return (-1); | |
} | |
else | |
std::cout << "Opened Database Successfully!" << std::endl; | |
//end of example code | |
//use while loop so multiple choices can be made before exiting | |
while (menuChoice != 0) { | |
//display user menu | |
std::cout << "Main Menu\n---------" << std::endl; | |
std::cout << "0 : Close connection and exit" << std::endl; | |
std::cout << "1 : Display database table" << std::endl; | |
std::cout << "2 : Display timetable" << std::endl; | |
std::cout << "3 : Generate new timetable (overwrite)" << std::endl; | |
std::cin >> menuChoice; | |
switch (menuChoice) | |
{ | |
case 0: | |
break; | |
case 1: | |
displayTable(db, exit, sqlErrorMessage); | |
break; | |
case 2: | |
displayTimetable(db, exit, sqlErrorMessage); | |
break; | |
case 3: | |
generateTimetable(db, exit, sqlErrorMessage); | |
break; | |
default: | |
std::cerr << "Invalid choice." << std::endl; | |
break; | |
} | |
} | |
sqlite3_close(db); //close connection to the database once finished | |
std::cout << "Closed connection to database." << std::endl; | |
return (0); | |
} |