1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179 | import time
import cPickle
import itertools
import random
from datetime import datetime
class TimeRange():
'''Simple class for checking if a time (or times) lie(s) within a time range.'''
datetimes = {}
def __init__(self, start, end):
'''Add datetime object to datetimes dict if not already there.'''
index = 0
for time in [start, end]:
if not index:
if time not in self.datetimes:
self.start = datetime.strptime(start, '%I:%M %p')
self.datetimes[time] = self.start
else:
self.start = self.datetimes[time]
elif index:
if time not in self.datetimes:
self.end = datetime.strptime(end, '%I:%M %p')
self.datetimes[time] = self.end
else:
self.end = self.datetimes[time]
index += 1
def __contains__(self, *args):
'''Return True if any of the inputs lies in the range.'''
# Construct list of time dicts
times = []
for time in args:
if time not in self.datetimes:
time_obj = datetime.strptime(time, '%I:%M %p')
self.datetimes[time] = time_obj
times.append(time_obj)
else:
times.append(self.datetimes[time])
# Check for conflicts
for time in times:
if (self.start <= time <= self.end):
return True
return False
def __show__(self):
'''Return time range as string.'''
return '{0}-{1}'.format(datetime.strftime(self.start, '%I:%M %p'), datetime.strftime(self.end, '%I:%M %p'))
def validate_inputs(courses):
'''Make sure inputs are valid courses.'''
with open('classes.pickle') as f:
all_courses = cPickle.load(f)
errors = []
# Catch any input errors and return them
for course in courses:
try:
abbrev, code = course.split(' ')
all_courses[abbrev][code]
except:
errors.append(course)
return errors
class Scheduler():
timeranges = {}
def __init__(self, courses, gender):
self.courses = courses
self.gender = gender
def get_course_lab_info(self, courses):
'''Return course and lab information in dict form.'''
# Get course data from file
with open('classes.pickle') as f:
all_courses = cPickle.load(f)
course_info = {}
# Loop through required courses
for course in courses:
abbrev, code = course.split(' ')
# If any error arises, return None and course that caused the error
course_sections = all_courses[abbrev][code]
course_labs = {}
# Loop through available sections
for crn, section in course_sections.items():
# If lab section, add to lab dict and remove from section dict
if 'Lab' in section['title']:
course_labs[crn] = course_sections.pop(crn)
# Add labs (if any) to course_info; key is course name with 'Lab' appended
if course_labs:
course_info[course], course_info[course + ' Lab'] = course_sections, course_labs
else:
course_info[course] = course_sections
return course_info
def is_conflict(self, section1, section2):
'''Check for a conflict between two sections.'''
# Get start and end times for each section
start1, end1, start2, end2 = section1['time'].split('-') + section2['time'].split('-')
# Create a TimeRange object for section1 if it's not in timeranges
text_time = section1['time']
if text_time in self.timeranges:
t = self.timeranges.get(text_time)
else:
t = TimeRange(start1, end1)
self.timeranges[text_time] = t
# Check if there is a conflict in days and/or time
if any(day in section2['days'] for day in section1['days']):
if t.__contains__(start2, end2):
return True
else:
return False
def check_conflicts(self, current_section, other_sections):
'''Return True if there are any conflicts.'''
return any([self.is_conflict(current_section, each) for each in other_sections])
def check_schedule_conflicts(self, schedule):
'''Check if a schedule contains conflicts.'''
sections = schedule.values()
for section in sections:
other_sections = [each for each in sections if each != section]
if self.check_conflicts(section, other_sections):
return True
return False
def filter_sections(self, course_info, gender):
'''Remove all sections that are not of provided gender.'''
for course, info in course_info.items():
for crn, section in info.items():
if section['gender'] != gender or section['instructor'] == 'TBA':
info.pop(crn)
return course_info
def generate_products(self, course_info):
'''Generate all possible schedule combinations.'''
possible_products = list(itertools.product(*course_info))
random.shuffle(possible_products)
for product in possible_products:
yield product
def start(self):
'''Start the scheduler.'''
# Get course info, filtered by gender
courses = self.filter_sections(self.get_course_lab_info(self.courses), self.gender)
# Get titles and corresponding sections
course_titles, course_info = courses.keys(), courses.values()
# Start finding valid schedules
schedules = []
start = time.time()
generate_products = self.generate_products(course_info)
for each in generate_products:
schedule = {}
for title, crn in zip(course_titles, each):
schedule[title] = courses[title][crn]
if not self.check_schedule_conflicts(schedule):
schedules.append(schedule)
if len(schedules) == 20 or (time.time() - start) >= 10:
break
return schedules
if __name__ == '__main__':
s = Scheduler(['MECH 390', 'PHYS 1120', 'GENG 215', 'HIS 133', 'CHEM 1701', 'MATH 2210', 'GENG 220'], 'G')
schedules = s.start()
|