def best_teaching_swap(schedule, courses, halls): """ Finds the best possible swap of a pair of teachings (if it exists), returning a new schedule with these teachings swapped. """ schedule_flat = functions.flatten_schedule(schedule) hall_count = len(halls) best_schedule = schedule best_score = score(schedule, courses) # initialise array for 'equivalent' schedules (having the same score) equiv_schedules = [] # check all possible swaps of teachings for i, old_teaching in enumerate(schedule_flat): for m, new_teaching in enumerate(schedule_flat[i + 1:]): # position of new_teaching in schedule_flat j = m + i + 1 # swap teachings if new_teaching: first_to_swap = classes.Teaching(new_teaching, hall=halls[i % hall_count]) else: first_to_swap = None if old_teaching: second_to_swap = classes.Teaching(old_teaching, hall=halls[j % hall_count]) else: second_to_swap = None # create new_schedule from schedule new_schedule = [None] * len(schedule) for k, row in enumerate(schedule): new_schedule[k] = list(row) new_schedule[i % hall_count][i // hall_count] = first_to_swap new_schedule[j % hall_count][j // hall_count] = second_to_swap # compute new score new_score = score(new_schedule, courses) if new_score > best_score: best_schedule, best_score = new_schedule, new_score # there are no 'equivalent' schedules anymore equiv_schedules = [] # remember equivalent schedule elif new_score == best_score: equiv_schedules.append(new_schedule) # choose randomly between the equivalent schedules if equiv_schedules: best_schedule = random.choice(equiv_schedules) return best_schedule
def random_fill_teachings(courses): """ Iterate over courses and make a list of teachings such that all seminar and practical groups are filled with students randomly. Returns a list of teachings. """ teachings = [] for course in courses: for _ in range(course.lectures): # assign students to lecture teachings.append( classes.Teaching("lecture", course, course.students)) if course.seminars: # randomly fill seminars with students seminars = functions.fill_teaching_groups(course, "seminar") for seminar in seminars: teachings.append(seminar) if course.practicals: # randomly fill seminars with students practicals = functions.fill_teaching_groups(course, "practical") for practical in practicals: teachings.append(practical) return teachings
def fill_teaching_groups(course, _type): """ Randomly fills all teachings of a course of a certain type with students, returning a list of filled teachings. """ group_count = course.get_group_count(_type) students_per_group = [0] * group_count if _type == "seminar": capacity = course.s_cap else: capacity = course.p_cap # initialise list of teaching groups groups = [classes.Teaching(_type, course, [], ALPHABET[i]) \ for i in range(group_count)] # insert each student into a random teaching group # provided that the group is not full yet for student in course.students: rand = random.randint(0, group_count - 1) while students_per_group[rand] == capacity: rand = random.randint(0, group_count - 1) groups[rand].students.append(student) students_per_group[rand] += 1 return groups
def alphabetical(courses, halls): """ Creates a schedule, filling all halls with teachings in the alphabetical order given by the file of courses. Returns a list of lists containing Teaching objects. """ # create a list of teachings to fill alphabetically teachings = [] for course in courses: for _ in range(course.lectures): # assign students to lecture teachings.append( classes.Teaching("lecture", course, course.students)) if course.seminars: # assign students to seminar groups (in alphabetical order) for i in range(course.get_group_count("seminar")): group = course.students[i * course.s_cap:(i + 1) * course.s_cap] teachings.append( classes.Teaching("seminar", course, group, ALPHABET[i])) if course.practicals: # assign students to practical groups (in alphabetical order) for i in range(course.get_group_count("practical")): group = course.students[i * course.p_cap:(i + 1) * course.p_cap] teachings.append( classes.Teaching("practical", course, group, ALPHABET[i])) # create an empty schedule of the right dimensions schedule = [[None] * TIMESLOTS for _ in range(len(halls))] # keep track of how many timeslots have been filled for each hall tracker = [0] * len(halls) # fill schedule with teachings for teaching in teachings: greedy_fill(schedule, teaching, halls, tracker) return schedule
def random_teaching_swap(schedule, courses, halls): """ Swaps two randomly selected teachings, returning the new schedule. """ schedule_flat = functions.flatten_schedule(schedule) hall_count = len(halls) teaching_count = len(schedule_flat) # select random teachings i = random.randrange(teaching_count) j = random.randrange(teaching_count) # ensure teachings are different while i == j: j = random.randrange(teaching_count) first_teaching = schedule_flat[i] second_teaching = schedule_flat[j] # swap teachings if second_teaching: first_to_swap = classes.Teaching(second_teaching, hall=halls[i % hall_count]) else: first_to_swap = None if first_teaching: second_to_swap = classes.Teaching(first_teaching, hall=halls[j % hall_count]) else: second_to_swap = None # create new_schedule from schedule new_schedule = [None] * len(schedule) for k, row in enumerate(schedule): new_schedule[k] = list(row) # swap teachings new_schedule[i % hall_count][i // hall_count] = first_to_swap new_schedule[j % hall_count][j // hall_count] = second_to_swap return new_schedule
def flatten_schedule(schedule, new_teachings=False): """ Flatten schedule in such a way that the teachings are sorted by timeslot. Schedule is a list of lists, where each list contains teachings scheduled at a certain hall. Returns flattened schedule. """ schedule_flat = [] for timeslot in zip(*schedule): for teaching in timeslot: if new_teachings: # create new Teaching object for teaching # if there exists a teaching at that timeslot if teaching: schedule_flat.append(classes.Teaching(teaching)) else: schedule_flat.append(None) else: schedule_flat.append(teaching) return schedule_flat