This commit is contained in:
SowinskiBraeden committed 2022-04-01 22:07:26 -07:00
1 parent 190daffced
commit de213b348a
7 files changed
+11728 -3030

No files matched your search

+2
View File
@@ -7,6 +7,8 @@
# Test output # Test output
/test/schedule.json /test/schedule.json
/test/students.json /test/students.json
/test/classes.json
/test/realCourses.json
# Sample Data # Sample Data
/test/course_selection_data.csv /test/course_selection_data.csv
+11610 -2967
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,4 +1,4 @@
courses = { mockCourses = {
"P-CL": { "P-CL": {
"name": "Pre-Calculus", "name": "Pre-Calculus",
"code": "P-CL", "code": "P-CL",
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/python3
import json
import csv
realCourses = {}
# Get all courses from real sample data
def getSampleCourses(log=False):
with open("course_selection_data.csv", newline='') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
exists = False
for course in realCourses:
exists = True if realCourses[course]["CrsNo"] == row["CrsNo"] else False
if exists: break
if not exists:
realCourses[row["CrsNo"]] = {
"CrsNo": row["CrsNo"],
"Requests": 0,
"Description": row["Description"],
"Credits": 4,
"Teachers": 3,
"students": []
}
if log:
with open("realCourses.json", "w") as outfile:
json.dump(realCourses, outfile, indent=2)
return realCourses
if __name__ == '__main__':
courseSet = getSampleCourses()
with open("realCourses.json", "w") as outfile:
json.dump(courseSet, outfile, indent=2)
+14 -6
View File
@@ -3,7 +3,7 @@ import random
import names import names
import json import json
import csv import csv
from courses import courses from courses import mockCourses
mockStudents = [] mockStudents = []
@@ -26,16 +26,16 @@ def generateMockStudents(n):
} }
# Get list of random class choices with no repeats # Get list of random class choices with no repeats
# 8 primary choices, 2 secondary choices # 8 primary choices, 2 secondary choices
courseSelection = random.sample(range(0, len(courses)), 10) courseSelection = random.sample(range(0, len(mockCourses)), 10)
for courseNum in courseSelection: for courseNum in courseSelection:
newStudent["requests"].append(list(courses)[courseNum]) newStudent["requests"].append(list(mockCourses)[courseNum])
mockStudents.append(newStudent) mockStudents.append(newStudent)
return mockStudents return mockStudents
# sort real sample data into usable dictionary # sort real sample data into usable dictionary
def getSampleStudents(): def getSampleStudents(log=False):
with open("course_selection_data.csv", newline='') as csvfile: with open("course_selection_data.csv", newline='') as csvfile:
reader = csv.DictReader(csvfile) reader = csv.DictReader(csvfile)
for row in reader: for row in reader:
@@ -45,14 +45,18 @@ def getSampleStudents():
if exists: break if exists: break
alternate = True if row["Alternate?"] == 'TRUE' else False alternate = True if row["Alternate?"] == 'TRUE' else False
if exists: if exists:
mockStudents[student["studentIndex"]]["requests"].append({"code":row["CrsNo"],"alt":alternate}) mockStudents[student["studentIndex"]]["requests"].append({
"CrsNo": row["CrsNo"],
"Description": row["Description"],
"alt": alternate
})
else: else:
newStudent = { newStudent = {
"Pupil #": row["Pupil #"], "Pupil #": row["Pupil #"],
"requests": [{ "requests": [{
"CrsNo": row["CrsNo"], "CrsNo": row["CrsNo"],
"Description": row["Description"], "Description": row["Description"],
"Alternate?": alternate "alt": alternate
}], }],
"schedule": { "schedule": {
"block1": "", "block1": "",
@@ -68,6 +72,10 @@ def getSampleStudents():
} }
mockStudents.append(newStudent) mockStudents.append(newStudent)
if log:
with open("students.json", "w") as outfile:
json.dump(mockStudents, outfile, indent=2)
return mockStudents return mockStudents
+3
View File
@@ -0,0 +1,3 @@
test_string = "XAT--12C-S"
print(bool([i for i in ["XAT--12A-S", "XAT--12B-S"] if(i in test_string)]))
+60 -56
View File
@@ -2,8 +2,9 @@
import sys import sys
import json import json
import math import math
from courses import courses, activeCourses from courses import mockCourses, activeCourses
from mockStudents import generateMockStudents from mockStudents import generateMockStudents, getSampleStudents
from generateCourses import getSampleCourses
''' '''
@@ -66,10 +67,10 @@ def generateScheduleV1():
for student in mockStudents: for student in mockStudents:
# Tally class request # Tally class request
for request in student["requests"]: for request in student["requests"]:
courses[request]["totalrequests"] += 1 mockCourses[request]["totalrequests"] += 1
courses[request]["studentindexes"].append(mockStudents.index(student)) mockCourses[request]["studentindexes"].append(mockStudents.index(student))
# Add course to active list if enough requests # Add course to active list if enough requests
if courses[request]["totalrequests"] > minReq and courses[request]["code"] not in activeCourses: activeCourses[courses[request]["code"]] = courses[request] if mockCourses[request]["totalrequests"] > minReq and mockCourses[request]["code"] not in activeCourses: activeCourses[mockCourses[request]["code"]] = mockCourses[request]
for student in mockStudents: for student in mockStudents:
alternateOffset = len(student["requests"])-8 alternateOffset = len(student["requests"])-8
@@ -85,7 +86,7 @@ def generateScheduleV1():
getFreeBlock = True getFreeBlock = True
while getFreeBlock: while getFreeBlock:
block = f"block{blockIndex}" block = f"block{blockIndex}"
cname = f"{courses[currentCourse]['name']}-{courseNum}" cname = f"{mockCourses[currentCourse]['name']}-{courseNum}"
if cname in running[block]: if cname in running[block]:
if student["schedule"][block] == "": # Add student to class if student["schedule"][block] == "": # Add student to class
if len(running[block][cname]["students"]) < classCap: if len(running[block][cname]["students"]) < classCap:
@@ -110,7 +111,7 @@ def generateScheduleV1():
generate = False generate = False
getFreeBlock = False getFreeBlock = False
else: else:
if (courses[currentCourse]["teachers"] - courseNum) > 0: if (mockCourses[currentCourse]["teachers"] - courseNum) > 0:
courseNum += 1 courseNum += 1
else: else:
blockIndex += 1 blockIndex += 1
@@ -146,7 +147,7 @@ def generateScheduleV1():
if cname not in running[newBlock] and len(running[newBlock]) < blockClassLimit: if cname not in running[newBlock] and len(running[newBlock]) < blockClassLimit:
if student["schedule"][newBlock] == "": # Add student to class if student["schedule"][newBlock] == "": # Add student to class
running[newBlock][cname] = { running[newBlock][cname] = {
"name": courses[currentCourse]["name"], "name": mockCourses[currentCourse]["name"],
"students": [student["name"]] "students": [student["name"]]
} }
student["schedule"][newBlock] = cname student["schedule"][newBlock] = cname
@@ -165,7 +166,7 @@ def generateScheduleV1():
err2 += 1 err2 += 1
break break
else: else:
if (courses[currentCourse]["teachers"] - courseNum) > 0: if (mockCourses[currentCourse]["teachers"] - courseNum) > 0:
courseNum += 1 courseNum += 1
else: else:
blockNum += 1 blockNum += 1
@@ -200,10 +201,10 @@ def generateScheduleV2():
for student in mockStudents: for student in mockStudents:
# Tally class request # Tally class request
for request in student["requests"]: for request in student["requests"]:
courses[request]["totalrequests"] += 1 mockCourses[request]["totalrequests"] += 1
courses[request]["studentindexes"].append(mockStudents.index(student)) mockCourses[request]["studentindexes"].append(mockStudents.index(student))
# Add course to active list if enough requests # Add course to active list if enough requests
if courses[request]["totalrequests"] > minReq and courses[request]["code"] not in activeCourses: activeCourses[courses[request]["code"]] = courses[request] if mockCourses[request]["totalrequests"] > minReq and mockCourses[request]["code"] not in activeCourses: activeCourses[mockCourses[request]["code"]] = mockCourses[request]
# calculate # of times to run class # calculate # of times to run class
for i in range(len(activeCourses)): for i in range(len(activeCourses)):
@@ -249,7 +250,7 @@ def generateScheduleV2():
if currentCourse in activeCourses: if currentCourse in activeCourses:
blockIndex = 1 blockIndex = 1
getFreeBlock = True getFreeBlock = True
cname = f"{courses[currentCourse]['name']}-{courseNum}" cname = f"{mockCourses[currentCourse]['name']}-{courseNum}"
while getFreeBlock: while getFreeBlock:
block = f"block{blockIndex}" block = f"block{blockIndex}"
if cname in running[block]: if cname in running[block]:
@@ -304,7 +305,7 @@ def generateScheduleV2():
generate = False generate = False
getFreeBlock = False getFreeBlock = False
else: else:
if (courses[currentCourse]["teachers"] - courseNum) > 0: if (mockCourses[currentCourse]["teachers"] - courseNum) > 0:
courseNum += 1 courseNum += 1
else: else:
blockIndex += 1 blockIndex += 1
@@ -322,7 +323,7 @@ def generateScheduleV2():
generate = False generate = False
break break
else: else:
if (courses[currentCourse]["teachers"] - courseNum) > 0: if (mockCourses[currentCourse]["teachers"] - courseNum) > 0:
courseNum += 1 courseNum += 1
else: else:
blockIndex += 1 blockIndex += 1
@@ -351,54 +352,62 @@ def generateScheduleV2():
# It starts by trying to get all classes full and give all students a full class list. # It starts by trying to get all classes full and give all students a full class list.
# Then it starts to attempt to fit all classes into a timetable, making corretions along # Then it starts to attempt to fit all classes into a timetable, making corretions along
# the way. Corrections being moving a students class # the way. Corrections being moving a students class
def generateScheduleV3(): def generateScheduleV3(students, courses):
# Step 1 - Calculate which classes can run # Step 1 - Calculate which classes can run
global err1, err2 global err1, err2
# Collect data and calculate schedules # Collect data and calculate schedules
for student in mockStudents: for student in students:
# Tally class request # Tally class request
for request in student["requests"]: for request in student["requests"]:
courses[request]["totalrequests"] += 1 if not bool([i for i in ["XAT--12A-S", "XAT--12B-S"] if (i in request["CrsNo"])]):
courses[request]["studentindexes"].append(mockStudents.index(student)) code = request["CrsNo"]
courses[code]["Requests"] += 1
# Add course to active list if enough requests # Add course to active list if enough requests
if courses[request]["totalrequests"] > minReq and courses[request]["code"] not in activeCourses: activeCourses[courses[request]["code"]] = courses[request] if courses[code]["Requests"] > minReq and courses[code]["CrsNo"] not in activeCourses: activeCourses[code] = courses[code]
# Step 2 - Generate class list without timetable # Step 2 - Generate class list without timetable
selectedCourses = {} selectedCourses = {}
# calculate # of times to run class # calculate # of times to run class
for i in range(len(activeCourses)): for i in range(len(activeCourses)):
index = list(activeCourses)[i] index = list(activeCourses)[i]
classRunCount = math.floor(activeCourses[index]["totalrequests"] / classCap) classRunCount = math.floor(activeCourses[index]["Requests"] / classCap)
# If there is minReq+ requests left, 1 more class could be run # If there is minReq+ requests left, 1 more class could be run
if (activeCourses[index]["totalrequests"] % classCap) > minReq: classRunCount += 1 if (activeCourses[index]["Requests"] % classCap) > minReq: classRunCount += 1
activeCourses[index]["classRunCount"] = classRunCount activeCourses[index]["classRunCount"] = classRunCount
classNum = classRunCount classNum = classRunCount
for student in mockStudents:
alternateOffset = len(student["requests"])-8 for student in students:
for j in range(len(student["requests"])-alternateOffset): # Subtract x classes as they are alternatives for request in (request for request in student["requests"] if not request["alt"]):
currentCourse = student["requests"][j] currentCourse = request["CrsNo"]
if currentCourse == activeCourses[index]["code"]: if currentCourse == activeCourses[index]["CrsNo"]:
cname = f"{activeCourses[index]['name']}-{classNum-(classRunCount-1)}" cname = f"{activeCourses[index]['Description']}-{classNum-(classRunCount-1)}"
if cname in selectedCourses: if cname in selectedCourses:
if student["name"] not in selectedCourses[cname]["students"]: if student["Pupil #"] not in selectedCourses[cname]["students"]:
if len(selectedCourses[cname]["students"]) < classCap: if len(selectedCourses[cname]["students"]) < classCap:
# Class exists and there is room # Class exists and there is room
selectedCourses[cname]["students"].append(student["name"]) selectedCourses[cname]["students"].append(student["Pupil #"])
elif len(selectedCourses[cname]["students"]) == classCap: elif len(selectedCourses[cname]["students"]) == classCap:
classRunCount -= 1 classRunCount -= 1
elif cname not in selectedCourses: elif cname not in selectedCourses:
selectedCourses[cname] = { selectedCourses[cname] = {
"students": [student["name"]], "students": [student["Pupil #"]],
"code": currentCourse "CrsNo": currentCourse,
"Description": courses[currentCourse]["Description"]
} }
for course in selectedCourses:
print("Class: ", selectedCourses[course]["Description"])
print("Students: ", len(selectedCourses[course]["students"]), "\n")
print("==============================")
print("Total: ", len(selectedCourses))
with open("classes.json", "w") as outfile: with open("classes.json", "w") as outfile:
json.dump(selectedCourses, outfile, indent=2) json.dump(selectedCourses, outfile, indent=2)
# Step 3 - Attempt to fit classes into timetable # Step 3 - Attempt to fit classes into timetable
# Step 4 - Evaluate, move classes or students to fix # Step 4 - Evaluate, move classes or students to fix
return []
if __name__ == '__main__': if __name__ == '__main__':
if len(sys.argv) == 1: if len(sys.argv) == 1:
@@ -425,23 +434,18 @@ if __name__ == '__main__':
mockStudents = generateMockStudents(studentsNum) mockStudents = generateMockStudents(studentsNum)
generateScheduleV2() generateScheduleV2()
elif sys.argv[1].upper() == 'V3': elif sys.argv[1].upper() == 'V3':
if len(sys.argv) == 3:
try:
studentsNum = int(sys.argv[2])
except:
print("Error parsing number of students")
exit()
print("Processing...") print("Processing...")
mockStudents = generateMockStudents(studentsNum) sampleStudents = getSampleStudents(True)
generateScheduleV3() samplemockCourses = getSampleCourses(True)
coursesList = generateScheduleV3(sampleStudents, samplemockCourses)
else: else:
print("Invalid argument") print("Invalid argument")
exit() exit()
print("\n") # print("\n")
print(f"Error 1: x{err1}") # print(f"Error 1: x{err1}")
print(f"Error 2: x{err2}") # print(f"Error 2: x{err2}")
print("\n") # print("\n")
### Displays the number of students in each class ### Displays the number of students in each class
# for block in running: # for block in running:
@@ -453,20 +457,20 @@ if __name__ == '__main__':
# print(f"Class: {name} | Students: {students}") # print(f"Class: {name} | Students: {students}")
# Count errors in students schedules # Count errors in students schedules
errors = 0 # errors = 0
for i in range(len(mockStudents)): # for i in range(len(mockStudents)):
count = 0 # count = 0
for course in mockStudents[i]["schedule"]: # for course in mockStudents[i]["schedule"]:
if mockStudents[i]["schedule"][course]=="": count+=1 # if mockStudents[i]["schedule"][course]=="": count+=1
if count > 0: errors += 1 # if count > 0: errors += 1
print(f"{errors}/{studentsNum} student(s) have a issue with their schedule") # print(f"{errors}/{studentsNum} student(s) have a issue with their schedule")
with open("schedule.json", "w") as outfile: # with open("schedule.json", "w") as outfile:
json.dump(running, outfile, indent=2) # json.dump(running, outfile, indent=2)
with open("students.json", "w") as outfile: # with open("students.json", "w") as outfile:
json.dump(mockStudents, outfile, indent=2) # json.dump(mockStudents, outfile, indent=2)
print("Done") print("Done")