begin refactor
This commit is contained in:
10 files changed
+323
-445
No files matched your search
+3
-1
@@ -2,9 +2,11 @@
|
||||
.vscode/
|
||||
|
||||
# testing
|
||||
*.py
|
||||
*test.c
|
||||
*.out
|
||||
|
||||
# output
|
||||
output/
|
||||
obj/
|
||||
bin/
|
||||
|
||||
@@ -28,4 +28,7 @@ $(BIN_DIR) $(OBJ_DIR):
|
||||
clean:
|
||||
@$(RM) -rv $(BIN_DIR) $(OBJ_DIR)
|
||||
|
||||
run:
|
||||
@$(BIN_DIR)/main
|
||||
|
||||
-include $(OBJ:.o=.d)
|
||||
+1
-1
@@ -33,7 +33,7 @@
|
||||
|
||||
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]);
|
||||
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
+287
-436
@@ -8,11 +8,14 @@
|
||||
#include "../include/courses.h"
|
||||
#include "../include/generator.h"
|
||||
|
||||
/*** for testing ***/
|
||||
#include<stdio.h>
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -39,46 +42,45 @@ uint8_t *equal(uint8_t *arr, size_t size) {
|
||||
}
|
||||
|
||||
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) {
|
||||
TIMETABLE_BLOCK defaultBlock;
|
||||
defaultBlock.numberOfClasses = 0;
|
||||
TIMETABLE timetable = {{defaultBlock}, false};
|
||||
/*
|
||||
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
|
||||
) {
|
||||
|
||||
uint8_t MEDIAN = floor((float) (MIN_REQ + CLASS_CAP) / 2);
|
||||
uint8_t BLOCKS_PER_SEMESTER = TOTAL_BLOCKS / 2;
|
||||
uint16_t activeCoursesLen = 0;
|
||||
|
||||
|
||||
/*** STEP 1 - Tally requests to check which courses are eligable to run ***/
|
||||
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;
|
||||
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) {
|
||||
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) {
|
||||
@@ -89,472 +91,321 @@ TIMETABLE generateTimetable(STUDENT *students, size_t size_students, COURSE *cou
|
||||
|
||||
if (!exists) {
|
||||
activeCourses[activeCoursesLen] = malloc(sizeof(char) * MAX_COURSE_NO_LEN);
|
||||
handle(activeCourses[activeCoursesLen], "'activeCourses[idx]' from generateTimetable");
|
||||
handleMalloc(activeCourses[activeCoursesLen], "'activeCourses[idx]' from calculateActiveCourses");
|
||||
strcpy(activeCourses[activeCoursesLen], courses[k].crsNo);
|
||||
activeCoursesIndexes[activeCoursesLen] = k;
|
||||
activeCoursesIndexes[activeCoursesLen] = k; // Save original index
|
||||
activeCoursesLen++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return activeCoursesLen;
|
||||
}
|
||||
|
||||
/*** 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
|
||||
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;
|
||||
|
||||
// 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++) {
|
||||
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, courses[index].crsNo);
|
||||
strcpy(newClass.baseCrsNo, course.crsNo);
|
||||
|
||||
char courseID[MAX_COURSE_ID_LEN];
|
||||
strcpy(courseID, courses[index].crsNo);
|
||||
appendChar(courseID, hex[j]);
|
||||
strcpy(courseID, course.crsNo);
|
||||
appendChar(courseID, id);
|
||||
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++;
|
||||
}
|
||||
strcpy(newClass.description, course.description);
|
||||
|
||||
//*** Handle remaining requests ***/
|
||||
newClass.numberOfStudents = numberOfStudents;
|
||||
|
||||
// Can we add remaining requests to existing classes
|
||||
bool remainingFitsInExistingClasses = remaining <= classRunCount * (CLASS_CAP - MEDIAN);
|
||||
classes[*classesLen] = newClass;
|
||||
courseClassIndexes[index] = *classesLen;
|
||||
(*classesLen)++;
|
||||
}
|
||||
|
||||
// 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
|
||||
void addRemainingToClasses(
|
||||
uint8_t classRunCount,
|
||||
uint8_t remaining,
|
||||
CLASS *classes,
|
||||
size_t *courseClassIndexes
|
||||
) {
|
||||
while (remaining > 0) {
|
||||
for (size_t j = 0; j < classRunCount; j++) {
|
||||
classes[courseClassIndexes[j]].numberOfStudents++;
|
||||
for (size_t i = 0; i < classRunCount; i++) {
|
||||
size_t index = courseClassIndexes[i];
|
||||
classes[index].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) {
|
||||
void equalizeNumberOfStudents(
|
||||
CLASS *classes,
|
||||
size_t *courseClassIndexes,
|
||||
uint8_t classRunCount
|
||||
) {
|
||||
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;
|
||||
handleMalloc(numberOfStudentsArr, "'numberOfStudentsArr' from equalizeNumberOfStudents");
|
||||
|
||||
numberOfStudentsArr = equal(numberOfStudentsArr, classRunCount);
|
||||
for (size_t j = 0; j < classRunCount; j++)
|
||||
classes[courseClassIndexes[j]].numberOfStudents = numberOfStudentsArr[j];
|
||||
|
||||
free(numberOfStudentsArr);
|
||||
for (size_t i = 0; i < classRunCount; i++) {
|
||||
size_t index = courseClassIndexes[i];
|
||||
numberOfStudentsArr[i] = classes[index].numberOfStudents;
|
||||
}
|
||||
|
||||
} else if (remainingPlusExtraFromExistingCanCreateNewClass) {
|
||||
// Take 1 student from each existing class till min requirement is met
|
||||
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 j = 0; j < classRunCount; j++) {
|
||||
classes[courseClassIndexes[j]].numberOfStudents--;
|
||||
for (size_t i = 0; i < *classRunCount; i++) {
|
||||
size_t index = courseClassIndexes[i];
|
||||
classes[index].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;
|
||||
createAndInsertClass(
|
||||
classes,
|
||||
classesLen,
|
||||
courseClassIndexes,
|
||||
*classRunCount,
|
||||
course,
|
||||
hex[*classRunCount],
|
||||
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
|
||||
*/
|
||||
(*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 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
|
||||
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;
|
||||
}
|
||||
|
||||
classes[courseClassIndexes[j]].numberOfStudents++;
|
||||
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);
|
||||
allClassRunCounts[i] = classRunCount;
|
||||
allClassRuns[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);
|
||||
// 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 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));
|
||||
handleMalloc(activeCourses, "'activeCourses' from generateTimetable");
|
||||
handleMalloc(activeCoursesIndexes, "'activeCoursesIndexes' from generateTimetable");
|
||||
|
||||
uint16_t activeCoursesLen = calculateActiveCourses(
|
||||
students,
|
||||
size_students,
|
||||
courses,
|
||||
size_courses,
|
||||
activeCourses,
|
||||
activeCoursesIndexes
|
||||
);
|
||||
|
||||
|
||||
/*** 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));
|
||||
handleMalloc(allClassRuns, "'allClassRuns' from generateTimetable");
|
||||
handleMalloc(classes, "'classes' from generateTimetable");
|
||||
size_t size_classes = generateEmptyClasses(
|
||||
courses,
|
||||
size_courses,
|
||||
activeCoursesIndexes,
|
||||
activeCoursesLen,
|
||||
allClassRuns,
|
||||
classes,
|
||||
MEDIAN
|
||||
);
|
||||
|
||||
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;
|
||||
|
||||
Executable
+22
@@ -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;
|
||||
}
|
||||
+2
-2
@@ -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
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user