#!/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'