diff --git a/.gitignore b/.gitignore index 7eeab0c..d47b2bd 100755 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,11 @@ .vscode/ # testing -*.py +*test.c +*.out # output output/ obj/ bin/ + diff --git a/Makefile b/Makefile index 21f3d9e..56419d1 100755 --- a/Makefile +++ b/Makefile @@ -28,4 +28,7 @@ $(BIN_DIR) $(OBJ_DIR): clean: @$(RM) -rv $(BIN_DIR) $(OBJ_DIR) +run: + @$(BIN_DIR)/main + -include $(OBJ:.o=.d) \ No newline at end of file diff --git a/include/main.h b/include/main.h index 02bf10b..bdf9892 100755 --- a/include/main.h +++ b/include/main.h @@ -33,8 +33,8 @@ static const char FLEX[][11] = {"XAT--12A-S", "XAT--12B-S"}; -void handle(void* mem, char name[]); +void handleMalloc(void* mem, char name[]); bool isFlex(char crsNo[MAX_COURSE_NO_LEN]); -#endif \ No newline at end of file +#endif diff --git a/src/courses.c b/src/courses.c index 5d7af77..dcf5b1e 100755 --- a/src/courses.c +++ b/src/courses.c @@ -28,7 +28,7 @@ UNIQUE_COURSES getNumberOfCourses(CSV_LINE *lines, size_t lines_len) { COURSE *getCourses(CSV_LINE *lines, size_t lines_len, UNIQUE_COURSES unique_course_info) { COURSE *courses = malloc(unique_course_info.numberOfCourses * sizeof(COURSE)); - handle(courses, "'courses' from getCourses"); + handleMalloc(courses, "'courses' from getCourses"); 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) { diff --git a/src/csv.c b/src/csv.c index 08bb219..362d557 100755 --- a/src/csv.c +++ b/src/csv.c @@ -36,7 +36,7 @@ CSV_LINE *csvReader(char data_dir[], size_t size) { } CSV_LINE *lines = malloc(size * sizeof(CSV_LINE)); - handle(lines, "'lines' from csvReader"); + handleMalloc(lines, "'lines' from csvReader"); char buff[MAX_CHAR]; int i = 0; diff --git a/src/generator.c b/src/generator.c index f3c45ce..67812cf 100755 --- a/src/generator.c +++ b/src/generator.c @@ -8,11 +8,14 @@ #include "../include/courses.h" #include "../include/generator.h" +/*** for testing ***/ +#include + const char hex[] = "0123456789abcdefABCDEF"; -void appendChar(char *str, char ch) { +void appendChar(char *str, char c) { int len = strlen(str); - str[len] = ch; + str[len] = c; str[len + 1] = '\0'; } @@ -34,527 +37,375 @@ uint8_t *equal(uint8_t *arr, size_t size) { for (size_t i = r; i < size; i++) arr[i] = q; - + return arr; } int stepIndex(int offset, StepType type, uint8_t blocksPerSemester) { - if (type == FirstToSecondSemester) - return (offset == 0 || offset == -1 * (blocksPerSemester - 1)) ? blocksPerSemester : (-1 * (blocksPerSemester - 1)); + if (type == FirstToSecondSemester) { + bool toNextSemester = offset == 0 || offset == -1 * (blocksPerSemester - 1); + return toNextSemester ? blocksPerSemester : (-1 * (blocksPerSemester - 1)); + } - if (type == SecondToFirstSemester) - return (offset == 0 || offset == blocksPerSemester + 1) ? (-1 * blocksPerSemester) : (blocksPerSemester + 1); + if (type == SecondToFirstSemester) { + bool toLastSemester = offset == 0 || offset == blocksPerSemester + 1; + return toLastSemester ? (-1 * blocksPerSemester) : (blocksPerSemester + 1); + } // Should not be possible return -1; } -TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *courses, size_t size_courses) { +/* + totals student requests for each course, + if course requests > MIN_REQ, course ID + is added to activeCourses. +*/ +uint16_t calculateActiveCourses( + STUDENT *students, + size_t size_students, + COURSE *courses, + size_t size_courses, + char **activeCourses, + uint16_t *activeCoursesIndexes +) { + + uint16_t activeCoursesLen = 0; + + 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 || + isFlex(students[i].requests[j].crsNo)) continue; + + for (size_t k = 0; k < size_courses; k++) { + if (strcmp(courses[k].crsNo, students[i].requests[j].crsNo) == 0) { + courses[k].requests++; + if (courses[k].requests >= MIN_REQ) { + bool exists = false; + for (size_t l = 0; l < activeCoursesLen; l++) { + if (strcmp(activeCourses[l], courses[k].crsNo) == 0) { + exists = true; + break; + } + } + + if (!exists) { + activeCourses[activeCoursesLen] = malloc(sizeof(char) * MAX_COURSE_NO_LEN); + handleMalloc(activeCourses[activeCoursesLen], "'activeCourses[idx]' from calculateActiveCourses"); + strcpy(activeCourses[activeCoursesLen], courses[k].crsNo); + activeCoursesIndexes[activeCoursesLen] = k; // Save original index + activeCoursesLen++; + } + } + break; + } + } + } + } + + return activeCoursesLen; +} + +void createAndInsertClass( + CLASS *classes, + size_t *classesLen, + size_t *courseClassIndexes, + size_t index, + COURSE course, + char id, + uint8_t numberOfStudents +) { + CLASS newClass; + strcpy(newClass.baseCrsNo, course.crsNo); + + char courseID[MAX_COURSE_ID_LEN]; + strcpy(courseID, course.crsNo); + appendChar(courseID, id); + strcpy(newClass.crsNo, courseID); + + strcpy(newClass.description, course.description); + + newClass.numberOfStudents = numberOfStudents; + + classes[*classesLen] = newClass; + courseClassIndexes[index] = *classesLen; + (*classesLen)++; +} + +void addRemainingToClasses( + uint8_t classRunCount, + uint8_t remaining, + CLASS *classes, + size_t *courseClassIndexes +) { + while (remaining > 0) { + for (size_t i = 0; i < classRunCount; i++) { + size_t index = courseClassIndexes[i]; + classes[index].numberOfStudents++; + remaining--; + if (remaining == 0) break; + } + } +} + +void equalizeNumberOfStudents( + CLASS *classes, + size_t *courseClassIndexes, + uint8_t classRunCount +) { + uint8_t *numberOfStudentsArr = malloc(classRunCount * sizeof(uint8_t)); + handleMalloc(numberOfStudentsArr, "'numberOfStudentsArr' from equalizeNumberOfStudents"); + + for (size_t i = 0; i < classRunCount; i++) { + size_t index = courseClassIndexes[i]; + numberOfStudentsArr[i] = classes[index].numberOfStudents; + } + + numberOfStudentsArr = equal(numberOfStudentsArr, classRunCount); + for (size_t i = 0; i < classRunCount; i++) { + size_t index = courseClassIndexes[i]; + classes[index].numberOfStudents = numberOfStudentsArr[i]; + } + + free(numberOfStudentsArr); +} + +void createClassWithRemaining( + COURSE course, + uint8_t *classRunCount, + uint8_t remaining, + CLASS *classes, + size_t *classesLen, + size_t *courseClassIndexes +) { + createAndInsertClass( + classes, + classesLen, + courseClassIndexes, + *classRunCount, + course, + hex[*classRunCount], + remaining + ); + + // Update class run count; if there is more than one class, equalize the class number of students + (*classRunCount)++; + if (*classRunCount >= 2) + equalizeNumberOfStudents(classes, courseClassIndexes, *classRunCount); +} + +void createClassWithRemainingAndExisting( + COURSE course, + uint8_t *classRunCount, + uint8_t remaining, + CLASS *classes, + size_t *classesLen, + size_t *courseClassIndexes +) { + // Take 1 student spot from each existing class till + // minimum requirement is met + while (remaining < MIN_REQ) { + for (size_t i = 0; i < *classRunCount; i++) { + size_t index = courseClassIndexes[i]; + classes[index].numberOfStudents--; + remaining++; + if (remaining == MIN_REQ) break; + } + } + + createAndInsertClass( + classes, + classesLen, + courseClassIndexes, + *classRunCount, + course, + hex[*classRunCount], + remaining + ); + + (*classRunCount)++; + equalizeNumberOfStudents(classes, courseClassIndexes, *classRunCount); +} + +void addRemainingToClassesTillFull( + size_t classRunCount, + size_t *courseClassIndexes, + CLASS *classes, + size_t remaining +) { + bool full = false; + while (!full) { + for (size_t i = 0; i < classRunCount; i++) { + size_t lastIndex = courseClassIndexes[classRunCount - 1]; + if (classes[lastIndex].numberOfStudents == CLASS_CAP) { + // If the last class is full, all classes before are + // assumed to be full ass well. + full = true; + break; + } + + size_t index = courseClassIndexes[i]; + if (classes[index].numberOfStudents == CLASS_CAP) continue; + classes[index].numberOfStudents++; + remaining--; + } + } +} + +/* + Creates an array of classes, + each with an estimaed number of students + per class. +*/ +size_t generateEmptyClasses( + COURSE *courses, + size_t size_courses, + uint16_t *activeCoursesIndexes, + uint16_t activeCoursesLen, + uint8_t *allClassRuns, + CLASS *classes, + uint8_t MEDIAN +) { + + 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; + + // Add 1 to classRunCount in case an extra class can be created with remaining + size_t *courseClassIndexes = malloc((classRunCount + 1) * sizeof(size_t)); + handleMalloc(courseClassIndexes, "'courseClassIndexes' from generateEmptyClasses"); + + for (size_t j = 0; j < classRunCount; j++) { + createAndInsertClass( + classes, + &classesLen, + courseClassIndexes, + j, + courses[index], + hex[j], + MEDIAN + ); + } + + /*** Handle remaining requests ***/ + + // Can add remaining requests to existing classes + bool remainingFitsInExisting = remaining <= classRunCount * (CLASS_CAP - MEDIAN); + + // Can create new class using only remaining requets + bool remainingCanCreateNewClass = remaining >= MIN_REQ; + + // Can create new class with remaining and borrowing requests from existing classes + bool remainingWithExistingCanCreateNewClass = MIN_REQ - remaining < classRunCount * (MEDIAN - MIN_REQ); + + if (remainingFitsInExisting) { + addRemainingToClasses(classRunCount, remaining, classes, courseClassIndexes); + } else if (remainingCanCreateNewClass) { + createClassWithRemaining( + courses[index], + &classRunCount, + remaining, + classes, + &classesLen, + courseClassIndexes + ); + } else if (remainingWithExistingCanCreateNewClass) { + createClassWithRemainingAndExisting( + courses[index], + &classRunCount, + remaining, + classes, + &classesLen, + courseClassIndexes + ); + } else { + addRemainingToClassesTillFull( + classRunCount, + courseClassIndexes, + classes, + remaining + ); + } + + free(courseClassIndexes); + allClassRuns[i] = classRunCount; + } + + // realloc classes to correct size + // CLASS *tempClasses = malloc(classesLen * sizeof(CLASS)); + // handleMalloc(tempClasses, "'tempClasses' from generateEmptyClasses"); + // 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); + + // ^^^ not needed if we are iterating using classesLen + // realloc is an expensive operation apparently. + + return classesLen; +} + +TIMETABLE generateTimetable( + STUDENT *students, + size_t size_students, + COURSE *courses, + size_t size_courses +) { + TIMETABLE_BLOCK defaultBlock; defaultBlock.numberOfClasses = 0; TIMETABLE timetable = {{defaultBlock}, false}; uint8_t MEDIAN = floor((float) (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 ***/ + + /*** STEP 1 - Tally requests to check which coures are eligble to run ***/ + // Array of course IDs that have met minimum requests. char **activeCourses = malloc(MAX_CLASSES * sizeof(char *)); uint16_t *activeCoursesIndexes = malloc(MAX_CLASSES * sizeof(uint16_t)); - handle(activeCourses, "'activeCourses' from generateTimetable"); - handle(activeCoursesIndexes, "'activeCoursesIndexes' from generateTimetable"); - 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 || isFlex(students[i].requests[j].crsNo)) continue; + handleMalloc(activeCourses, "'activeCourses' from generateTimetable"); + handleMalloc(activeCoursesIndexes, "'activeCoursesIndexes' from generateTimetable"); - for (size_t k = 0; k < size_courses; k++) { - if (strcmp(courses[k].crsNo, students[i].requests[j].crsNo) == 0) { - courses[k].requests++; - if (courses[k].requests >= MIN_REQ) { - if (activeCoursesLen == 0) { - activeCourses[activeCoursesLen] = malloc(sizeof(char) * MAX_COURSE_NO_LEN); - handle(activeCourses[activeCoursesLen], "'activeCourses[idx]' from generateTimetable"); - strcpy(activeCourses[activeCoursesLen], courses[k].crsNo); - activeCoursesIndexes[activeCoursesLen] = k; - activeCoursesLen++; - } else { - bool exists = false; - for (size_t l = 0; l < activeCoursesLen; l++) { - if (strcmp(activeCourses[l], courses[k].crsNo) == 0) { - exists = true; - break; - } - } + uint16_t activeCoursesLen = calculateActiveCourses( + students, + size_students, + courses, + size_courses, + activeCourses, + activeCoursesIndexes + ); + - if (!exists) { - activeCourses[activeCoursesLen] = malloc(sizeof(char) * MAX_COURSE_NO_LEN); - handle(activeCourses[activeCoursesLen], "'activeCourses[idx]' from generateTimetable"); - strcpy(activeCourses[activeCoursesLen], courses[k].crsNo); - activeCoursesIndexes[activeCoursesLen] = k; - activeCoursesLen++; - } - } - } - break; - } - } - } - } - - - /*** STEP 2 - Generate classes with no students, but calculate the number of expected students per class ***/ - - uint8_t *allClassRunCounts = malloc(activeCoursesLen * sizeof(uint8_t)); - // max this out to total number of classrooms available between both semesters + /*** STEP 2 - Generate classes with no students + * - Calculate number of expected students per class ***/ + uint8_t *allClassRuns = malloc(activeCoursesLen * sizeof(uint8_t)); CLASS *classes = malloc(CLASSROOMS * TOTAL_BLOCKS * sizeof(CLASS)); - handle(allClassRunCounts, "'allClassRunCounts' from generateTimetable"); - handle(classes, "'classes' from generateTimetable"); - 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; + handleMalloc(allClassRuns, "'allClassRuns' from generateTimetable"); + handleMalloc(classes, "'classes' from generateTimetable"); + size_t size_classes = generateEmptyClasses( + courses, + size_courses, + activeCoursesIndexes, + activeCoursesLen, + allClassRuns, + classes, + MEDIAN + ); - // add 1 to classRunCount in case we need to create an extra class with remaining - size_t *courseClassIndexes = malloc((classRunCount + 1) * sizeof(size_t)); - handle(courseClassIndexes, "'courseClassIndexes' from generateTimetable"); - for (size_t j = 0; j < classRunCount; j++) { - CLASS newClass; - 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 - - classes[classesLen] = newClass; - courseClassIndexes[j] = classesLen; - classesLen++; - } - - //*** 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++) { - classes[courseClassIndexes[j]].numberOfStudents++; - remaining--; - if (remaining == 0) break; - } - } - - } else if (remainingCanCreateNewClass) { - // Create new class - CLASS newClass; - 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); - strcpy(newClass.description, courses[index].description); - newClass.numberOfStudents = remaining; - - // Insert class into empty classes array and update index - 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)); - handle(numberOfStudentsArr, "'numberOfStudentsArr' from generateTimetable"); - for (size_t j = 0; j < classRunCount; j++) - numberOfStudentsArr[j] = classes[courseClassIndexes[j]].numberOfStudents; - - numberOfStudentsArr = equal(numberOfStudentsArr, classRunCount); - for (size_t j = 0; j < classRunCount; j++) - classes[courseClassIndexes[j]].numberOfStudents = numberOfStudentsArr[j]; - - free(numberOfStudentsArr); - } - - } else if (remainingPlusExtraFromExistingCanCreateNewClass) { - // Take 1 student from each existing class till min requirement is met - while (remaining < MIN_REQ) { - for (size_t j = 0; j < classRunCount; j++) { - classes[courseClassIndexes[j]].numberOfStudents--; - remaining++; - if (remaining == MIN_REQ) break; - } - } - - // Create new class with remaining - CLASS newClass; - 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); - strcpy(newClass.description, courses[index].description); - newClass.numberOfStudents = remaining; - - // Insert class into empty classes array and update index - classes[classesLen] = newClass; - courseClassIndexes[classRunCount] = classesLen; - classesLen++; - classRunCount++; - - // Equalize the class number of students - uint8_t *numberOfStudentsArr = malloc(classRunCount * sizeof(uint8_t)); - handle(numberOfStudentsArr, "'numberOfStudentsArr' from generateTimetable"); - for (size_t j = 0; j < classRunCount; j++) - numberOfStudentsArr[j] = classes[courseClassIndexes[j]].numberOfStudents; - - numberOfStudentsArr = equal(numberOfStudentsArr, classRunCount); - for (size_t j = 0; j < classRunCount; j++) - classes[courseClassIndexes[j]].numberOfStudents = numberOfStudentsArr[j]; - - free(numberOfStudentsArr); - - } 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 - */ - - bool full = false; - while (!full) { - for (size_t j = 0; j < classRunCount; j++) { - 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; - } - - classes[courseClassIndexes[j]].numberOfStudents++; - remaining--; - } - } - } - - free(courseClassIndexes); - allClassRunCounts[i] = classRunCount; - } - - // realloc classes to correct size - CLASS *tempClasses = malloc(classesLen * sizeof(CLASS)); - handle(tempClasses, "'tempClasses' from generateTimetable"); - 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); free(activeCoursesIndexes); - /*** STEP 3 - Insert students into empty classes ***/ - STUDENT *tempStudents = malloc(size_students * sizeof(STUDENT)); - handle(tempStudents, "'tempStudents' from generateTimetable"); - size_t size_tempStudents = size_students; - memcpy(tempStudents, students, size_students * sizeof(STUDENT)); - - uint8_t *currentInserted = malloc(classesLen * sizeof(uint8_t)); - handle(currentInserted, "'currentInserted' from generateTimetable"); - 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]; - - // 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)); - handle(alternates, "'alternates' from generateTimetable"); - 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; - - while (getAvailableCourse) { - for (size_t j = 0; j < classesLen; j++) { - // Class exists in classes - if (strcmp(classes[j].baseCrsNo, course) == 0) { - // 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; - student.classes++; - 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)); - handle(tempAlternates, "'tempAlternates' from generateTimetable"); - 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) { - 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)); - handle(tempAlternates, "'tempAlternates' from generateTimetable"); - 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; - } - } - } - } - } - - // Asign remaining alternates && classes 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; - - students[i].classes = student.classes; - 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)); - handle(new_tempStudents, "'new_tempStudents' from generateTimetable"); - 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); - /*** STEP 4 - Insert classes into timetable ***/ - while (activeCoursesLen > 0) { - // Find highest resource class (most times run) - size_t index = 0; - uint8_t max = 0; - for (size_t i = 0; i < activeCoursesLen; i++) { - if (allClassRunCounts[i] > max) { - max = allClassRunCounts[i]; - index = i; - } - } - - // Tally first semester and second semester - uint8_t allSemesterBlockLens[TOTAL_BLOCKS] = {0}; - for (uint8_t i = 0; i < TOTAL_BLOCKS; i++) - allSemesterBlockLens[i] = timetable.blocks[i].numberOfClasses; - - // If there is more than one class running - if (allClassRunCounts[index] > 1) { - // Get index of block with least class run counts - uint8_t minClassrooms = CLASSROOMS; - uint8_t blockIndex = 0; - for (uint8_t i = 0; i < TOTAL_BLOCKS; i++) { - if (allSemesterBlockLens[i] < minClassrooms) { - minClassrooms = allSemesterBlockLens[i]; - blockIndex = i; - } - } - - StepType step = blockIndex < BLOCKS_PER_SEMESTER ? FirstToSecondSemester : SecondToFirstSemester; - int offset = 0; - - // Disperse classes throughout both semesters - // Find base class ID index - size_t indexOffset = 0; - size_t baseIndex = 0; - for (size_t i = 0; i < classesLen; i++) { - if (strcmp(classes[i].baseCrsNo, activeCourses[index]) == 0) { - baseIndex = i; - break; - } - } - uint8_t classRunCounts = allClassRunCounts[index]; - for (size_t i = 0; i < classRunCounts; i++) { - bool classInserted = false; - while (!classInserted) { - blockIndex += offset; - - // Insert class - if (timetable.blocks[blockIndex].numberOfClasses < CLASSROOMS) { - uint8_t classIndex = timetable.blocks[blockIndex].numberOfClasses; - timetable.blocks[blockIndex].classes[classIndex] = classes[baseIndex + indexOffset]; - timetable.blocks[blockIndex].numberOfClasses++; - allClassRunCounts[index]--; - indexOffset++; - classInserted = true; - } - - // Update offset and to get index of block for next semester - offset = stepIndex(offset, step, BLOCKS_PER_SEMESTER); - if (blockIndex >= (TOTAL_BLOCKS - 1)) { - blockIndex = step == FirstToSecondSemester ? 0 : BLOCKS_PER_SEMESTER; - offset = 0; - } - } - } - - // If the class only runs once, place in semester with least classes - } else if (allClassRunCounts[index] == 1) { - // Get index of block with least class run counts - uint8_t minClassrooms = CLASSROOMS; - uint8_t blockIndex = 0; - for (uint8_t i = 0; i < TOTAL_BLOCKS; i++) { - if (allSemesterBlockLens[i] < minClassrooms) { - minClassrooms = allSemesterBlockLens[i]; - blockIndex = i; - } - } - - // Get class ID index - size_t baseIndex = 0; - for (size_t i = 0; i < classesLen; i++) { - if (strcmp(classes[i].baseCrsNo, activeCourses[index]) == 0) { - baseIndex = i; - break; - } - } - - // Insert class - uint8_t classIndex = timetable.blocks[blockIndex].numberOfClasses; - timetable.blocks[blockIndex].classes[classIndex] = classes[baseIndex]; - timetable.blocks[blockIndex].numberOfClasses++; - allClassRunCounts[index]--; - } - - // Now that the classRunCount is 0 since all classes have been inserted into the timetable - // delete this course as it has been handled - activeCoursesLen--; - - // create temp arrays of data - uint8_t *tempAllClassRunCounts = malloc(activeCoursesLen * sizeof(uint8_t)); - char **tempActiveCourses = malloc(activeCoursesLen * sizeof(char*)); - handle(tempAllClassRunCounts, "'tempAllClassRunCounts' from generateTimetable"); - handle(tempActiveCourses, "'tempActiveCourses' from generateTimetable"); - - // Copy data without the course we just handled - size_t tempIndex = 0; - for (size_t i = 0; i <= activeCoursesLen; i++) { - if (activeCourses[i] == activeCourses[index]) { - free(activeCourses[i]); - continue; - }; - - tempAllClassRunCounts[tempIndex] = allClassRunCounts[i]; - tempActiveCourses[tempIndex] = activeCourses[i]; - tempIndex++; - } - - // Reallocate the arrays of course data to be 1 less in size and copy temp data back to them, then free temp data - allClassRunCounts = realloc(allClassRunCounts, activeCoursesLen * sizeof(uint8_t)); - activeCourses = realloc(activeCourses, activeCoursesLen * sizeof(char*)); - memcpy(allClassRunCounts, tempAllClassRunCounts, activeCoursesLen * sizeof(uint8_t)); - memcpy(activeCourses, tempActiveCourses, activeCoursesLen * sizeof(char*)); - free(tempAllClassRunCounts); - free(tempActiveCourses); - } - - - // STEP 5 - fill student schedule - for (size_t i = 0; i < TOTAL_BLOCKS; i++) { - for (size_t j = 0; j < timetable.blocks[i].numberOfClasses; j++) { - for (size_t k = 0; k < timetable.blocks[i].classes[j].numberOfStudents; k++) { - for (size_t l = 0; l < size_students; l++) { - if (students[l].pupilNum == timetable.blocks[i].classes[j].students[k]) { - strcpy(students[l].schedule[i], timetable.blocks[i].classes[j].crsNo); - break; - } - } - } - } - } - - - // STEP 6 - Solve student schedule errors - + /*** Done ***/ free(classes); - free(allClassRunCounts); + free(allClassRuns); + for (size_t i = 0; i < activeCoursesLen; i++) + free(activeCourses[i]); free(activeCourses); return timetable; diff --git a/src/hash.c b/src/hash.c new file mode 100755 index 0000000..3284d28 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,22 @@ +typedef struct { + const char *key; + void *value; +} hashEntry; + +struct hashTable { + hashEntry entries; + size_t capacity; + size_t length; +} + +hashTable* createHashTable(void) { + hashTable *table = malloc(sizeof(hashTable)); + handleMalloc(table, "'table' from createHashTable"); + table->length = 0; + table->capacity = 64; + + table->entries = calloc(table->capacity, sizeof(hashEntry)); + handleMalloc(table->entries, "'table->entries' from createHashTable"); + + return table; +} \ No newline at end of file diff --git a/src/main.c b/src/main.c index fd6ada9..1e78b0c 100755 --- a/src/main.c +++ b/src/main.c @@ -11,7 +11,7 @@ #include "../include/json.h" #include "../include/generator.h" -void handle(void *mem, char name[]) { +void handleMalloc(void *mem, char name[]) { if (mem == NULL) { fprintf(stderr, "Failed to allocate memory! - Memory: %s\n", name); exit(-1); @@ -24,7 +24,7 @@ bool isFlex(char crsNo[MAX_COURSE_NO_LEN]) { return false; } -int main(int argc, char **argv) { +int main(void) { /* read csv data into array of structs that can be processed into an array of student structs diff --git a/src/students.c b/src/students.c index 151b718..b9cef5c 100755 --- a/src/students.c +++ b/src/students.c @@ -40,7 +40,7 @@ STUDENT *getStudents(CSV_LINE *lines, size_t lines_len, int total_blocks, UNIQUE // Create our student array with the correct number of students STUDENT *students = malloc(students_info.numberOfStudents * sizeof(STUDENT)); - handle(students, "'students' from getStudents"); + handleMalloc(students, "'students' from getStudents"); for (uint16_t i = 0; i < students_info.numberOfStudents; i++) { STUDENT student; @@ -52,14 +52,14 @@ STUDENT *getStudents(CSV_LINE *lines, size_t lines_len, int total_blocks, UNIQUE student.remainingAltsLen = 0; REQUEST *requests = malloc(numRequests[i] * sizeof(REQUEST)); - handle(requests, "'requests' from getStudents"); + handleMalloc(requests, "'requests' from getStudents"); student.requests = requests; for (uint8_t i = 0; i < TOTAL_BLOCKS; i++) strcpy(student.schedule[i], i < TOTAL_BLOCKS / 2 ? FLEX[0] : FLEX[1]); REQUEST *remainingAlts = malloc(MAX_REQUEST_ALTS * sizeof(REQUEST)); - handle(remainingAlts, "'remainingAlts' from getStudents"); + handleMalloc(remainingAlts, "'remainingAlts' from getStudents"); student.remainingAlts = remainingAlts; students[i] = student; diff --git a/test b/test new file mode 100755 index 0000000..e52ed5c Binary files /dev/null and b/test differ