spacepaste

  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()