#!/usr/bin/env python import re import os import sys import getopt def ConvertToMilitaryTime(timestring): timestring = timestring.rstrip('A') if timestring.endswith('P'): timestring = timestring[:-1] if not timestring.startswith('12'): timestring = int(timestring) + 1200 return int(timestring) def ParseCourseGroup(mainterm, lines): courselist = [] # First line looks like 'MAA 4200 MODERN ANALYSIS ' workstr = lines[0].rstrip()[:-4] symbol = workstr[0:8] name = workstr[9:] # 001 C 11705 30 3 3 ED 113 MWF 10:30-11:35AM Boca Raton KALIES, WILLIAM D. # Prerequisite course required MAC 2312 # and Prerequisite course required MAD 2104 # 001 C 11709 100 1 3 GS 118 MWF 09:15-10:20AM Boca Raton PINA, PHILIP A # Activity corequisite required LAB # Gordon Rule Course # First make one pass looking for corequisites coreq = {} for line in lines[1:]: if line.count('corequisite'): req = line[68:76] if req.startswith('LAB '): req = 'L'.join(symbol.split()) elif req.startswith('LEC '): req = symbol[0:3] + ' ' + symbol[4:] elif req.startswith('DIS '): req = 'D'.join(symbol.split()) if add_coreqs: coreq[req] = 1 continue # Now get actual course data for line in lines[1:]: # Skip lines that don't have times in them try: if line[56] != ':': continue except IndexError: continue # Get various course info coursenum = line[16:21] credits = line[33] termspec = line[14].strip() if termspec: term = mainterm + termspec else: term = mainterm days = line[46:54].rstrip() time = line[54:66].rstrip().replace(':', '') try: starttime, endtime = time.split('-') except ValueError: continue try: starttime = ConvertToMilitaryTime(starttime) endtime = ConvertToMilitaryTime(endtime) except ValueError: continue # Times that start in AM and end in PM if ((endtime - starttime) > 1200): starttime += 1200 # Replace MAC 2023 with MACL2023 for labs # BSC 1011 with BSCD1011 for Discussions if name == 'Laboratory': symbol = 'L'.join(symbol.split()) elif name == 'Discussion': symbol = 'D'.join(symbol.split()) courselist.append(Course(symbol, name, coursenum, term, days, starttime, endtime, coreq)) return courselist def ParseCourselines(lines): term_re = re.compile(' Courses For (\S+) ') i = 0 for line in lines: try: mainterm = term_re.search(line).group(1).lower() except AttributeError: pass if line.startswith('
'): break i += 1 # Split groups up by their course title coursegroups = ''.join(lines[i:]).split('
')[1:] courselist = [] for group in coursegroups: courselist.extend(ParseCourseGroup(mainterm, group.split('\n'))) return courselist class Course(dict): def __init__(self, symbol, name, coursenum, term, days, starttime, endtime, coreq): self['symbol'] = symbol self['name'] = name self['days'] = days self['term'] = term self['coursenum'] = coursenum self['starttime'] = starttime self['endtime'] = endtime self['coreq'] = coreq def conflict_time(self, course): for day in self['days']: if day in course['days']: if self['starttime'] > course['endtime']: continue if course['starttime'] > self['endtime']: continue return 1 return 0 def conflicts_with(self, courselist): for course in courselist: if self['term'] == 'summerA': if course['term'] == 'summerB': continue if self['term'] == 'summerB': if course['term'] == 'summerA': continue if self['term'].startswith('summer') and \ course['term'].startswith('summer'): if self.conflict_time(course): return 1 return 0 def __str__(self): return '%-30s %s %s %s %s %d %d' % (self['name'], self['symbol'], self['coursenum'], self['term'], self['days'], self['starttime'], self['endtime']) def coreqs_passed(courselist): symbols = {} coreqs = {} for course in courselist: symbols[course['symbol']] = 1 for coreq in course['coreq']: coreqs[coreq] = 1 for coreq in coreqs: if coreq not in symbols: return 0 return 1 def add_schedule(minimum_schedule, courselist): # Only work on schedules if they meet minimum length if len(courselist) < minimum_schedule: return # Check conflicts new_courselist = [] for course in courselist: if not course.conflicts_with(new_courselist): new_courselist.append(course) # Check coreqs if not coreqs_passed(new_courselist): return strings = [] for course in new_courselist: strings.append(str(course)) strings.sort() global_schedule['\n'.join(strings)] = new_courselist def print_courses(courselist): for course in courselist: print course print def build(courselist, minimum_schedule, selection = []): # Create a list of courses that are the same type as courselist[0] # Split the rest into course_rest course_roots = [] course_rest = [] for course in courselist: if course['symbol'] == courselist[0]['symbol']: course_roots.append(course) else: course_rest.append(course) if not course_roots: add_schedule(minimum_schedule, selection[:]) # Now loop through these possible roots for root_course in course_roots: # Add the root course selection.append(root_course) # Loop through rest of courses for course in course_rest: selection.append(course) # Remove all courses of type that was just added to selection new_courselist = [] for i in course_rest: if i['symbol'] != course['symbol']: new_courselist.append(i) # Recursively call with remaining items build(new_courselist, minimum_schedule, selection) selection.pop() # Edge case, add the schedule if not course_rest: add_schedule(minimum_schedule, selection[:]) # Remove the root course selection.pop() def usage(): print 'Usage:\n\ncourses.py -d listing_dir -c "CLASS 1,CLASS 2,CLASS 3" [-n] [-m #]\n' print '-n = Do not add corequisites' print '-m = Minimum number of courses to be in a schedule' try: opts, args = getopt.getopt(sys.argv[1:], "d:c:nm:") except getopt.GetoptError: usage() sys.exit(1) add_coreqs = 1 min_courses = 1 listing_dir = '' wanted = '' for opt, arg in opts: if opt == "-d": listing_dir = arg elif opt == "-c": wanted = [ x.strip() for x in arg.split(',')] elif opt == "-n": add_coreqs = 0 elif opt == "-m": min_courses = int(arg) if not listing_dir or not wanted: usage() sys.exit(1) # Load the course list courselist = [] for html in os.listdir(listing_dir): lines = file(listing_dir + os.sep + html).readlines() courselist.extend(ParseCourselines(lines)) print '%d courses loaded' % len(courselist) # Make sure wanted list has all corequisites for course in wanted: for avail in courselist: if avail['symbol'] == course: for coreq in avail['coreq']: if coreq not in wanted: print 'Adding corequisite %s to wanted list' % coreq wanted.append(coreq) # Reduce courselist to only wanted courses newcourselist = [] for i in courselist: if i['symbol'] in wanted: newcourselist.append(i) courselist = newcourselist print 'Reduced to %d courses\n' % len(courselist) global_schedule = {} buildlist = build(courselist, min_courses) for (x, courselist) in global_schedule.items(): printlist = [] for course in courselist: printlist.append(str(course)) printlist.sort() print '\n'.join(printlist), '\n'