From e8fc0e2138a2e8420b7c0b0f89078f131a252b5f Mon Sep 17 00:00:00 2001 From: SowinskiBraeden Date: Thu, 3 Oct 2024 23:09:02 -0700 Subject: [PATCH] begin step 2 --- main.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 146 insertions(+), 11 deletions(-) diff --git a/main.c b/main.c index b17ae8a..ecbf973 100644 --- a/main.c +++ b/main.c @@ -23,7 +23,7 @@ #define MAX_PUPIL_NUM_LEN 8 #define MAX_COURSE_DES_LEN 50 #define MAX_COURSE_NO_LEN 21 -#define MAX_COURSE_ID_LEN MAX_COURSE_NO_LEN + 4 // 4 includes the underscore and the 3 digit unique number to identify the course +#define MAX_COURSE_ID_LEN MAX_COURSE_NO_LEN + 3 // 3 includes the underscore and the 2 digit unique number to identify the course #define TOTAL_BLOCKS 10 // the number of blocks between 2 semesters i.e 8 = 4 blocks per semester, 10 = 5 blocks per semester #define MAX_REQUEST_ALTS 6 @@ -36,6 +36,8 @@ // 5 is the length of "FALSE" & 3 is the number of commas per line #define MAX_CHAR MAX_PUPIL_NUM_LEN + MAX_COURSE_NO_LEN + MAX_COURSE_DES_LEN + 5 + 3 +const char FLEX[2][11] = {"XAT--12A-S", "XAT--12B-S"}; + /*** DEFINE CSV READER FUNCTIONS & DATASTRUCTURES ***/ typedef struct { @@ -132,9 +134,16 @@ typedef struct { uint16_t requests; char description[MAX_COURSE_DES_LEN]; uint8_t credits; + uint32_t students[CLASS_CAP * CLASSROOMS * TOTAL_BLOCKS]; // Will be large enough to handle all pupil numbers taking this course, even if it is highly requested. + uint8_t globalNumberOfStudents; +} COURSE; // Course is the general information for selection and stuff? + +typedef struct { + char crsNo[MAX_COURSE_NO_LEN]; + char description[MAX_COURSE_DES_LEN]; uint32_t students[CLASS_CAP]; uint8_t numberOfStudents; -} COURSE; +} CLASS; // A class is an actual active course, there can be many classes for 1 course, each with its own number of students. /*** DEFINE GET FUNCTIONS FOR STUDENTS & COURSES FROM CSV ***/ @@ -252,7 +261,7 @@ COURSE *getCourses(CSV_LINE *lines, size_t lines_len, UNIQUE_COURSES unique_cour for (size_t i = 0; i < unique_course_info.numberOfCourses; i++) { for (size_t j = 0; j < lines_len; j++) { if (strcmp(lines[j].crsNo, unique_course_info.uniqueCrsNos[j]) == 0) { - COURSE course = {"\0", 0, "\0", 4, {0}, 0}; // Ensure array of pupil numbers is null till we populate it later with the correct size + COURSE course = {"\0", 0, "\0", 4, 0}; // Ensure array of pupil numbers is null till we populate it later with the correct size strcpy(course.crsNo, lines[i].crsNo); strcpy(course.description, lines[i].description); courses[i] = course; @@ -357,17 +366,17 @@ int writeCoursesToJson(COURSE *courses, size_t numberOfCourses, char output_dir[ fprintf(stream, " \"description\": \"%s\",\n", courses[i].description); fprintf(stream, " \"credits\": %d,\n", courses[i].credits); - if (courses[i].numberOfStudents > 0) { + if (courses[i].globalNumberOfStudents > 0) { fprintf(stream, " \"students\": [\n"); - for (size_t j = 0; j < courses[i].numberOfStudents; j++) { - fprintf(stream, " \"%d\"%s\n", courses[i].students[j], j == courses[i].numberOfStudents - 1 ? "" : ","); + for (size_t j = 0; j < courses[i].globalNumberOfStudents; j++) { + fprintf(stream, " \"%d\"%s\n", courses[i].students[j], j == courses[i].globalNumberOfStudents - 1 ? "" : ","); } fprintf(stream, " ],\n"); } else { fprintf(stream, " \"students\": [],\n"); } - fprintf(stream, " \"numberOfStudents\": %d\n", courses[i].numberOfStudents); + fprintf(stream, " \"globalNumberOfStudents\": %d\n", courses[i].globalNumberOfStudents); fprintf(stream, " }%s\n", i == numberOfCourses - 1 ? "" : ","); } @@ -384,18 +393,45 @@ typedef struct { bool success; } TIMETABLE; -char FLEX[2][11] = {"XAT--12A-S", "XAT--12B-S"}; +void appendChar(char *str, char ch) { + int len = strlen(str); + str[len] = ch; + str[len + 1] = '\0'; +} + +// Equally disperses the sum of an array to each index +uint8_t *equal(uint8_t *arr, size_t size) { + size_t sum = 0; + + // Calculate the sum of the array + for (size_t i = 0; i < size; i++) + sum += arr[i]; + + // Calculate quotient and remainder + size_t q = sum / size; + size_t r = sum % size; + + // Fill the result array + for (size_t i = 0; i < r; i++) + arr[i] = q + 1; + + for (size_t i = r; i < size; i++) + arr[i] = q; + + return arr; +} TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *courses, size_t size_courses) { TIMETABLE timetable = {{{{0}}}, 0}; - uint8_t median = floor((MIN_REQ + CLASS_CAP) / 2); - uint8_t blocksPerSemester = TOTAL_BLOCKS / 2; + uint8_t MEDIAN = floor((MIN_REQ + CLASS_CAP) / 2); + uint8_t BLOCKS_PER_SEMESTER = TOTAL_BLOCKS / 2; + // Step 1 - Tally requests to check which courses are eligable to run char activeCourses[MAX_COURSES][MAX_COURSE_NO_LEN] = {{"\0"}}; size_t activeCoursesIndexes[MAX_COURSES] = {0}; - uint16_t activeCourseIndex = 0; + uint16_t activeCourseIndex = 0; // also acts as the length of activeCourses for (size_t i = 0; i < size_students; i++) { for (size_t j = 0; j < students[i].requestsLen; j++) { if (students[i].requests[j].alternate) continue; @@ -432,6 +468,105 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou } } + + // Step 2 - Generate classes with no students, but calculate the number of expected students per class + + char hex[] = "0123456789abcdefABCDEF"; // Used to get a unique character to identify different classes of the same course + + CLASS *emptyClasses = malloc(CLASSROOMS * TOTAL_BLOCKS * sizeof(CLASS)); // max this out to total number of classrooms available between both semesters + size_t currentIndex = 0; + for (size_t i = 0; i < activeCourseIndex; i++) { + uint16_t index = activeCoursesIndexes[i]; + uint8_t classRunCount = floor(courses[index].requests / MEDIAN); + uint8_t remaining = courses[index].requests % MEDIAN; + + size_t *courseClassIndexes = malloc((classRunCount + 1) * sizeof(size_t)); // add 1 to classRunCount in case we need to create an extra class with remaining + for (size_t j = 0; j < classRunCount; j++) { + CLASS newClass; + char courseID[MAX_COURSE_NO_LEN + 3]; // +3 = "_n\0" where n is the ID + strcpy(courseID, courses[index].crsNo); + appendChar(courseID, hex[j]); + strcpy(newClass.crsNo, courseID); + strcpy(newClass.description, courses[index].description); + newClass.numberOfStudents = MEDIAN; // The expected number of students in this class + + // CLASS_HASH emptyClass; + // strcpy(emptyClass.key, courses[index].crsNo); + // emptyClass.class = newClass; + // emptyClasses[currentIndex] = emptyClass; + emptyClasses[currentIndex] = newClass; + courseClassIndexes[j] = currentIndex; + currentIndex++; + } + + // Handle remaining requests + + // Can we add remaining requests to existing classes + bool remainingFitsInExistingClasses = remaining <= classRunCount * (CLASS_CAP - MEDIAN); + // Can we create a new class using only remaining requests + bool remainingCanCreateNewClass = remaining >= MIN_REQ; + // Can we create a new class if we borrow students from created classes to add to remaining requests to meet min req + bool remainingPlusExtraFromExistingCanCreateNewClass = MIN_REQ - remaining < classRunCount * (MEDIAN - MIN_REQ); + + if (remainingFitsInExistingClasses) { + // Simply add remaining to existing classes + while (remaining > 0) { + for (size_t j = 0; j < classRunCount; j++) { + emptyClasses[courseClassIndexes[j]].numberOfStudents++; + remaining--; + } + } + + } else if (remainingCanCreateNewClass) { + // Create new class + CLASS newClass; + char courseID[MAX_COURSE_NO_LEN + 3]; // +3 = "_n\0" where n is the ID + strcpy(courseID, courses[index].crsNo); + appendChar(courseID, hex[classRunCount]); + strcpy(newClass.crsNo, courseID); + strcpy(newClass.description, courses[index].description); + newClass.numberOfStudents = remaining; + + // Insert class into empty classes array and update index + emptyClasses[currentIndex] = newClass; + courseClassIndexes[classRunCount] = currentIndex; + currentIndex++; + + // update class run count; if there is more than one class, equalize the class number of students + classRunCount++; + if (classRunCount >= 2) { + uint8_t *numberOfStudentsArr = malloc(classRunCount * sizeof(uint8_t)); + for (size_t j = 0; j < classRunCount; j++) + numberOfStudentsArr[j] = emptyClasses[courseClassIndexes[j]].numberOfStudents; + + numberOfStudentsArr = equal(numberOfStudentsArr, classRunCount); + for (size_t j = 0; j < classRunCount; j++) + emptyClasses[courseClassIndexes[j]].numberOfStudents = numberOfStudentsArr[j]; + + free(numberOfStudentsArr); + } + + } else if (remainingPlusExtraFromExistingCanCreateNewClass) { + // TODO handle this logic + + } else { + /* + If all above cannot handle remaining requests we will add as many of + the remaining requests to the existing classes. Any number of requests + that dont fit will be ignored so later they can be folded into their + alternative choices + */ + + // TODO: handle this logic too + } + + free(courseClassIndexes); + } + + // DO MORE ALGORITHM + + free(emptyClasses); + return timetable; }