diff --git a/include/courses.h b/include/courses.h index 6d94d22..27b68fb 100644 --- a/include/courses.h +++ b/include/courses.h @@ -20,7 +20,8 @@ typedef struct { } COURSE; // Course is the general information for selection and stuff? typedef struct { - char crsNo[MAX_COURSE_NO_LEN]; + char crsNo[MAX_COURSE_ID_LEN]; + char baseCrsNo[MAX_COURSE_NO_LEN]; char description[MAX_COURSE_DES_LEN]; uint32_t students[CLASS_CAP]; uint8_t numberOfStudents; diff --git a/src/main.c b/src/main.c index b027f61..e5e96a4 100644 --- a/src/main.c +++ b/src/main.c @@ -53,10 +53,10 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou // uint8_t BLOCKS_PER_SEMESTER = TOTAL_BLOCKS / 2; - // Step 1 - Tally requests to check which courses are eligable to run + /*** 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; // also acts as the length of activeCourses + uint16_t activeCoursesLen = 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; @@ -67,13 +67,13 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou if (strcmp(courses[k].crsNo, students[i].requests[j].crsNo) == 0) { courses[k].requests++; if (courses[k].requests >= MIN_REQ) { - if (activeCourseIndex == 0) { - strcpy(activeCourses[activeCourseIndex], courses[k].crsNo); - activeCoursesIndexes[activeCourseIndex] = k; - activeCourseIndex++; + if (activeCoursesLen == 0) { + strcpy(activeCourses[activeCoursesLen], courses[k].crsNo); + activeCoursesIndexes[activeCoursesLen] = k; + activeCoursesLen++; } else { bool exists = false; - for (size_t l = 0; l < activeCourseIndex; l++) { + for (size_t l = 0; l < activeCoursesLen; l++) { if (strcmp(activeCourses[l], courses[k].crsNo) == 0) { exists = true; break; @@ -81,9 +81,9 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou } if (!exists) { - strcpy(activeCourses[activeCourseIndex], courses[k].crsNo); - activeCoursesIndexes[activeCourseIndex] = k; - activeCourseIndex++; + strcpy(activeCourses[activeCoursesLen], courses[k].crsNo); + activeCoursesIndexes[activeCoursesLen] = k; + activeCoursesLen++; } } } @@ -94,43 +94,44 @@ 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 + /*** 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 + char hex[] = "0123456789abcdefABCDEF"; - uint8_t *allClassRunCounts = malloc(activeCourseIndex * sizeof(uint8_t)); - 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++) { + uint8_t *allClassRunCounts = malloc(activeCoursesLen * sizeof(uint8_t)); + // max this out to total number of classrooms available between both semesters + CLASS *classes = malloc(CLASSROOMS * TOTAL_BLOCKS * sizeof(CLASS)); + size_t classesLen = 0; + for (size_t i = 0; i < activeCoursesLen; i++) { uint16_t index = activeCoursesIndexes[i]; uint8_t classRunCount = floor((float) 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 + // add 1 to classRunCount in case we need to create an extra class with remaining + size_t *courseClassIndexes = malloc((classRunCount + 1) * sizeof(size_t)); 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(newClass.baseCrsNo, courses[index].crsNo); + char courseID[MAX_COURSE_ID_LEN]; 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++; + classes[classesLen] = newClass; + courseClassIndexes[j] = classesLen; + classesLen++; } - // Handle remaining requests + //*** 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); @@ -138,7 +139,7 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou // Simply add remaining to existing classes while (remaining > 0) { for (size_t j = 0; j < classRunCount; j++) { - emptyClasses[courseClassIndexes[j]].numberOfStudents++; + classes[courseClassIndexes[j]].numberOfStudents++; remaining--; if (remaining == 0) break; } @@ -147,7 +148,8 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou } else if (remainingCanCreateNewClass) { // Create new class CLASS newClass; - char courseID[MAX_COURSE_NO_LEN + 3]; // +3 = "_n\0" where n is the ID + strcpy(newClass.baseCrsNo, courses[index].crsNo); + char courseID[MAX_COURSE_ID_LEN]; strcpy(courseID, courses[index].crsNo); appendChar(courseID, hex[classRunCount]); strcpy(newClass.crsNo, courseID); @@ -155,20 +157,20 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou newClass.numberOfStudents = remaining; // Insert class into empty classes array and update index - emptyClasses[currentIndex] = newClass; - courseClassIndexes[classRunCount] = currentIndex; - currentIndex++; + classes[classesLen] = newClass; + courseClassIndexes[classRunCount] = classesLen; + classesLen++; // 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[j] = classes[courseClassIndexes[j]].numberOfStudents; numberOfStudentsArr = equal(numberOfStudentsArr, classRunCount); for (size_t j = 0; j < classRunCount; j++) - emptyClasses[courseClassIndexes[j]].numberOfStudents = numberOfStudentsArr[j]; + classes[courseClassIndexes[j]].numberOfStudents = numberOfStudentsArr[j]; free(numberOfStudentsArr); } @@ -177,7 +179,7 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou // Take 1 student from each existing class till min requirement is met while (remaining < MIN_REQ) { for (size_t j = 0; j < classRunCount; j++) { - emptyClasses[courseClassIndexes[j]].numberOfStudents--; + classes[courseClassIndexes[j]].numberOfStudents--; remaining++; if (remaining == MIN_REQ) break; } @@ -185,7 +187,8 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou // Create new class with remaining CLASS newClass; - char courseID[MAX_COURSE_NO_LEN + 3]; // +3 = "_n\0" where n is the ID + strcpy(newClass.baseCrsNo, courses[index].crsNo); + char courseID[MAX_COURSE_ID_LEN]; strcpy(courseID, courses[index].crsNo); appendChar(courseID, hex[classRunCount]); strcpy(newClass.crsNo, courseID); @@ -193,19 +196,19 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou newClass.numberOfStudents = remaining; // Insert class into empty classes array and update index - emptyClasses[currentIndex] = newClass; - courseClassIndexes[classRunCount] = currentIndex; - currentIndex++; + classes[classesLen] = newClass; + courseClassIndexes[classRunCount] = classesLen; + classesLen++; classRunCount++; // Equalize the class number of students uint8_t *numberOfStudentsArr = malloc(classRunCount * sizeof(uint8_t)); for (size_t j = 0; j < classRunCount; j++) - numberOfStudentsArr[j] = emptyClasses[courseClassIndexes[j]].numberOfStudents; + numberOfStudentsArr[j] = classes[courseClassIndexes[j]].numberOfStudents; numberOfStudentsArr = equal(numberOfStudentsArr, classRunCount); for (size_t j = 0; j < classRunCount; j++) - emptyClasses[courseClassIndexes[j]].numberOfStudents = numberOfStudentsArr[j]; + classes[courseClassIndexes[j]].numberOfStudents = numberOfStudentsArr[j]; free(numberOfStudentsArr); @@ -220,13 +223,13 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou bool full = false; while (!full) { for (size_t j = 0; j < classRunCount; j++) { - if (emptyClasses[courseClassIndexes[classRunCount - 1]].numberOfStudents == CLASS_CAP) { + if (classes[courseClassIndexes[classRunCount - 1]].numberOfStudents == CLASS_CAP) { // If the last class in the array is at class_cap, all other classes must be at class cap and we are full full = true; break; } - emptyClasses[courseClassIndexes[j]].numberOfStudents++; + classes[courseClassIndexes[j]].numberOfStudents++; remaining--; } } @@ -236,9 +239,155 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou allClassRunCounts[i] = classRunCount; } + // realloc classes to correct size + CLASS *tempclasses = malloc(classesLen * sizeof(CLASS)); + for (size_t i = 0; i < classesLen; i++) + tempclasses[i] = classes[i]; + classes = realloc(classes, classesLen * sizeof(CLASS)); + memcpy(classes, tempclasses, classesLen * sizeof(CLASS)); + free(tempclasses); + + + /*** STEP 3 - Insert students into empty classes ... yikes ***/ + STUDENT *tempStudents = malloc(size_students * sizeof(STUDENT)); + size_t size_tempStudents = size_students; + memcpy(tempStudents, students, size_students * sizeof(STUDENT)); + + uint8_t *currentInserted = malloc(classesLen * sizeof(uint8_t)); + for (size_t i = 0; i < classesLen; i++) + currentInserted[i] = 0; + + while (size_tempStudents > 0) { + // Choose student at random, to prevent success bias to students first in the array + STUDENT student = tempStudents[rand() % size_tempStudents]; + // printf("pupilNum: %d\n", student.pupilNum); + + // Create an array of students alternates + size_t numberOfAlts = 0; + for (size_t i = 0; i < student.requestsLen; i++) + if (student.requests[i].alternate) + numberOfAlts++; + + REQUEST *alternates = malloc(numberOfAlts * sizeof(REQUEST)); + size_t alternateIdx = 0; + for (size_t i = 0; i < student.requestsLen; i++) { + if (student.requests[i].alternate) { + alternates[alternateIdx] = student.requests[i]; + alternateIdx++; + } + } + + // Search existing classes to insert student based off request + for (size_t i = 0; i < student.requestsLen; i++) { + if (student.requests[i].alternate) continue; // Ignore alternates + char course[MAX_COURSE_NO_LEN] = {"\0"}; + strcpy(course, student.requests[i].crsNo); + bool getAvailableCourse = true; + bool isAlt = false; + // printf("getting available course for %s...\n", course); + while (getAvailableCourse) { + // printf("searching...\n"); + for (size_t j = 0; j < classesLen; j++) { + // Class exists in classes + // printf("comparing target %s to %s at idx %ld\n", course, classes[j].crsNo, j); + if (strcmp(classes[j].baseCrsNo, course) == 0) { + // printf("course %s exists\n", course); + // If this is an alternate, and there is room to expand, increase number of students to allow extra + if (isAlt && classes[j].numberOfStudents < CLASS_CAP) + classes[j].numberOfStudents++; + + // Class exists with room for student + if (currentInserted[j] < classes[j].numberOfStudents) { + classes[j].students[currentInserted[j]] = student.pupilNum; + currentInserted[j]++; + getAvailableCourse = false; + break; + + } else if (currentInserted[j] == classes[j].numberOfStudents) { + // If class is full, and there's no more classes available for that course, convert to alt + if (j == classesLen - 1 || (j != classesLen - 1 && strcmp(classes[j + 1].baseCrsNo, course) != 0)) { + if (numberOfAlts > 0) { + // Use alternate + strcpy(course, alternates[0].crsNo); // asign alternate to course and retry + // remove alternate from array of alts to retry same alt over and over + REQUEST *tempAlternates = malloc(numberOfAlts * sizeof(REQUEST)); + memcpy(tempAlternates, alternates, numberOfAlts * sizeof(REQUEST)); + numberOfAlts--; + alternates = realloc(alternates, numberOfAlts * sizeof(REQUEST)); + for (size_t k = 1; k <= numberOfAlts; k++) + alternates[k - 1] = tempAlternates[k]; + free(tempAlternates); + isAlt = true; + break; + } else { + // Force break the loop, ignore as it cannot be resolved + // Allow administrator to handle error manually + getAvailableCourse = false; + break; + } + } + } + // this class does not exist, i.e not enough requests + } else if (j == classesLen - 1) { + // printf("course %s does not exist\n", course); + if (numberOfAlts > 0) { + // Use alternate + strcpy(course, alternates[0].crsNo); // asign alternate to course and retry + // remove alternate from array of alts to retry same alt over and over + REQUEST *tempAlternates = malloc(numberOfAlts * sizeof(REQUEST)); + memcpy(tempAlternates, alternates, numberOfAlts * sizeof(REQUEST)); + numberOfAlts--; + alternates = realloc(alternates, numberOfAlts * sizeof(REQUEST)); + for (size_t k = 1; k <= numberOfAlts; k++) + alternates[k - 1] = tempAlternates[k]; + free(tempAlternates); + isAlt = true; + break; + } else { + // Force break the loop, ignore as it cannot be resolved + // Allow administrator to handle error manually + getAvailableCourse = false; + break; + } + } + } + } + // printf("got available course for %s...\n", course); + } + + // Asign remaining alternates to student + for (size_t i = 0; i < size_students; i++) { + if (students[i].pupilNum == student.pupilNum) { + students[i].remainingAlts = realloc(students[i].remainingAlts, numberOfAlts * sizeof(REQUEST)); + memcpy(students[i].remainingAlts, alternates, numberOfAlts * sizeof(REQUEST)); + students[i].remainingAltsLen = numberOfAlts; + break; + } + } + free(alternates); + + // realloc tempStudents to be size_tempStudents - 1 without struct of student just processed + size_tempStudents--; + STUDENT *new_tempStudents = malloc(size_tempStudents * sizeof(STUDENT)); + size_t idx = 0; + for (size_t i = 0; i <= size_tempStudents; i++) { + if (tempStudents[i].pupilNum != student.pupilNum) { + new_tempStudents[idx] = tempStudents[i]; + idx++; + } + } + + tempStudents = realloc(tempStudents, size_tempStudents * sizeof(STUDENT)); + memcpy(tempStudents, new_tempStudents, size_tempStudents * sizeof(STUDENT)); + free(new_tempStudents); + } + + free(currentInserted); + free(tempStudents); + // DO MORE ALGORITHM - free(emptyClasses); + free(classes); free(allClassRunCounts); return timetable;