Problem Set 8 - Due Tuesday November 13

Reading

  1. Think Python, Ch. 14: Files
  2. New material: Slides and notebooks from Lec 16 Sorting and lambda and Lec 17 File Operations
  3. Older material that's still very relevant: slides and notebooks from Lec 13 Dictionaries and Lec 14 Accumulation Pattern with Lists and Dictionaries
  4. Lab 08 Dictionaries, Lab 09 Data Analysis and Visualization, Lab 10 File Operations, JSON, Dictionaries and Tuples

About this Problem Set

The purpose of this problem set is to give you practice with five concepts:

There are two tasks:

Notes:


Task 1: Using Memory Diagrams to Understand a Buggy Dictionary Function

Subtask 1a: Memory Diagram for a Correct Version of createSurvivalDictionary

Here's one correct solution to the createSurvivalDictionary function from the PS07 Task 3a Titanic problem:

def createSurvivalDictionaryCorrect(passengers):
    """A correct solution for createSurvivalDictionary from PS07 Task 3a"""

    # Step 1: find the unique cabin classes
    cabinClasses = set([passenger['class'] for passenger in passengers])

    # Step 2: populate dict with mini-dicts
    survDict = {}
    for cabin in cabinClasses:
        survDict[cabin] = {'survivors': 0, 'victims': 0}

    # Step 3: iterate through the list of passengers and update the mini-dicts
    for passenger in passengers:
        if passenger['status'] == 'survivor':
            survDict[passenger['class']]['survivors'] += 1
        else:  # if status is not survivor' it is 'victim'
            survDict[passenger['class']]['victims'] += 1

    # Step 4: updating mini-dicts to contain the survivalRate
    for cabin in survDict:
        numSurvivors = survDict[cabin]['survivors']
        numVictims = survDict[cabin]['victims']
        classSize = numSurvivors + numVictims
        survDict[cabin]['survivalRate'] = round(numSurvivors/float(classSize), 3)

    return survDict

Consider the following test list with five passengers:

testPassengers = [
    {'name': 'Mr Anthony Abbing',
     'status': 'victim',
     'age': '42.0',
     'class': '3rd Class', 
     'job': 'Blacksmith'}, 
    {'name': 'Mrs Leah Aks',
     'status': 'survivor',
     'age': '18.0',
     'class': '3rd Class'},
    {'name': 'Mr Albert Akerman',
     'status': 'victim',
     'age': '28.0',
     'class': 'Victualling', 
     'job': 'Steward'},  
    {'name': 'Miss Ebba Iris Alfrida Andersson',
     'status': 'victim',
     'age': '6.0',
     'class': '3rd Class'},
    {'name': 'Mr Arthur John Bright',
     'status': 'survivor',
     'age': '42.0',
     'class': 'Deck', 
     'job': 'Quartermaster'}
]

In this part, you will draw one memory diagram that shows the (nested) dictionary value returned by the following function call:

    createSurvivalDictionaryCorrect(testPassengers)

Notes:

Subtask 1b: Memory Diagram for a Correct Version of createSurvivalDictionary lines

Here's a buggy version of the createSurvivalDictionary function:

def createSurvivalDictionaryBuggy(passengers):
    """A buggy solution for createSurvivalDictionary from PS07 Task 3a"""

    # Step 1: find the unique cabin classes
    cabinClasses = set([passenger['class'] for passenger in passengers])

    # Step 2: populate dict with mini-dicts
    survDict = {}
    miniDict = {'survivors': 0, 'victims': 0} # changed from correct version
    for cabin in cabinClasses:
        survDict[cabin] = miniDict # changed from correct version

    # Step 3: iterate through the list of passengers and update the mini-dicts
    for passenger in passengers:
        if passenger['status'] == 'survivor':
            survDict[passenger['class']]['survivors'] += 1
        else:  # if status is not survivor' it is 'victim'
            survDict[passenger['class']]['victims'] += 1

    # Step 4: updating mini-dicts to contain the survivalRate
    for cabin in survDict:
        numSurvivors = survDict[cabin]['survivors']
        numVictims = survDict[cabin]['victims']
        classSize = numSurvivors + numVictims
        survDict[cabin]['survivalRate'] = round(numSurvivors/float(classSize), 3)

    return survDict

The only difference between createSurvivalDictionaryBuggy and createSurvivalDictionaryCorrect is that in n Step 2 of createSurvivalDictionaryBuggy, the correct lines

    # Step 2: populate dict with mini-dicts
    survDict = {}
    for cabin in cabinClasses:
        survDict[cabin] = {'survivors': 0, 'victims': 0}

from createSurvivalDictionaryCorrect have been changed to:

    # Step 2: populate dict with mini-dicts
    survDict = {}
    miniDict = {'survivors': 0, 'victims': 0} # changed from correct version
    for cabin in cabinClasses:
        survDict[cabin] = miniDict # changed from correct version

Although this change seems minor, the aliasing introduced by this change causes createSurvivalDictionaryBuggy to have the wrong behavior.

In this part, you will draw one memory diagram for the survival dictionary returned by the call

    createSurvivalDictionaryBuggy(testPassengers)

that shows why createSurvivalDictionaryBuggy has the wrong behavior. Follow the same notes from the first subtask.


Task 2: Wellesley Distribution Requirements

This task is a Partner-recommended problem in which it is recommended that you work with a partner as part of a two-person team. Use this shared Google Doc to find a pair programming partner.

Background

At Wellesley College, students are expected to fulfill several distribution requirements during their four years. The Course Browser website displays the code of each distribution together with course information. For example, CS 111 fulfills MM (Mathematical Modeling), PSYC 215 fulfills both EC (Epistemology and Cognition) and SBA (Social Behavior Analysis), and CHEM 205 fulfills MM, NPS (Natural and Physical Sciences) and QRF (Quantitative Reasoning Overlay).

Often students want to choose courses based on the distribution requirement(s) they fulfill. For example, they might want to know which courses from different departments fulfill a certain distribution. In this task, you’ll write code that takes per-course data about distribution requirements and reorganizes it in terms of distribution requirements, department, and enrollments.

All input files for this task can be found in the ps08/inputFiles folder. This task will write its text file outputs into the ps08/outputFiles folder.

The main input file is spring17courses.txt, which shows the distribution requirements satisfied by Wellesley courses taught in the Spring, 2017 semester. Here are some sample lines from that file:

AFR 213 - 01 | 11 | SBA
AFR 243 - 01 | 21 | ARS,REP
CHEM 205 - 03 | 28 | MM,QRF,NPS
WRIT 132 - 01 | 14 | W
WRIT 111 - 01 | 14 | ARS,W
WRIT 390 - 01 | 11 | LL,SBA

Each line has three pieces of information separated by the vertical bar character, |:

  1. The course, which includes (a) a department, (b) a course number, and (c) a section number. The course number and section number are separated by a dash surrounded by a space on both sides;

  2. The enrollment = number of students in that section of the course in Spring, 2017;

  3. The distribution codes for the distribution requirement(s) fulfilled by the course. If there are more than one, they are separated by commas.

In this task, you will be fleshing out several function skeletons in the file distributions.py in the ps08 folder.

There are eight subtasks for this task. That may seem like a lot, but we have broken up some big problems into manageable pieces, many of which require defining functions that require writing only a few lines of code.

Also, we have designed the problems in such a way that you can work on later subtasks even if you haven't correctly solved previous subtasks. See details in the notes for each subtask.

Subtask a: Extract information from a course description line

In this subtask, you will define a courseLineToTuple function that takes a string representing a single line of the spring17courses.txt file and returns a tuple with the following four pieces of information:

  1. The course name, which is a string consists of the department and course number, separated by a space.

  2. The section number (as a string, not a number; e.g. '01', not 1)

  3. The enrollment (as a number, not a string; e.g. 17, not '17')

  4. A list of distribution codes for the course, where each distribution code is string. E.g., the distribution code list for AFR 213 is ['SBA'], the list for WRIT 111 is ['ARS', 'W'], and the list for CHEM 205 is ['MM', 'QRF', 'NPS'].

You should assume that the input string ends in a newline character. For example:

In []: courseLineToTuple('AFR 213 - 01 | 11 | SBA\n')
Out[]: ('AFR 213', '01', 11, ['SBA'])

In []: courseLineToTuple('WRIT 111 - 01 | 14 | ARS,W\n')
Out[]: ('WRIT 111', '01', 14, ['ARS', 'W'])

In []: courseLineToTuple('CHEM 205 - 03 | 28 | MM,QRF,NPS\n')
Out[]: ('CHEM 205', '03', 28, ['MM', 'QRF', 'NPS'])

Note:

Subtask b: Read data from a file of course description lines

In this subtask, you will define a readCourseTuplesFromFile function that takes a string specifying a path to a file (such as inputFiles/spring17courses.txt) and returns a list of four-tuples of the form

    (course name, section, enrollment, [distCode1, distCode2, ...]) 

that are returned by courseLineToTuple.

Since the inputFiles/spring17courses.txt is rather long (538 lines), we have provide two shorter files inputFiles/subset20.txt (20 lines) and inputFiles/subset50.txt (50 lines) that are subsets of inputFiles/spring17courses.txt that are helpful in testing.

For example:

In []: readCourseTuplesFromFile('inputFiles/subset20.txt')
Out[]: 
[('AFR 213', '01', 11, ['SBA']),
 ('AFR 243', '01', 21, ['ARS', 'REP']),
 ('AFR 295', '01', 20, ['HS', 'LL']),
 ('ASTR 101', '01', 36, ['MM', 'NPS']),
 ('ASTR 101', '02', 35, ['MM', 'NPS']),
 ('CHEM 102', '01', 24, ['NPS']),
 ('CHEM 105', '01', 11, ['MM', 'NPS']),
 ('CHEM 205', '01', 25, ['MM', 'QRF', 'NPS']),
 ('CHIN 223', '01', 14, ['HS', 'LL']),
 ('CHIN 231', '01', 12, ['EC', 'LL']),
 ('CS 111', '01', 22, ['MM']),
 ('CS 111', '02', 20, ['MM']),
 ('CS 111', '03', 17, ['MM']),
 ('CS 111', '04', 17, ['MM']),
 ('CS 349', '02', 32, ['MM', 'EC']),
 ('ECON 103', '01', 32, ['QRF', 'SBA']),
 ('ECON 103', '02', 23, ['QRF', 'SBA']),
 ('ECON 103', '03', 19, ['QRF', 'SBA']),
 ('ENG 112', '01', 31, ['ARS', 'LL']),
 ('ENG 295', '01', 20, ['HS', 'LL'])]

Notes:

Subtask c: Group courses by distribution requirement

Define a function groupCoursesByDistribution that takes a list of course tuples like those created in Subtask 2b and returns a dictionary whose keys are the distribution requirement codes and whose values are subdictionaries containing all courses that fulfill that distribution. Each subdictionary (which we'll call a course subdictionary) should have keys that are course names and values that are the total enrollment over all sections of that course.

For example, suppose that tuples20 is the list of tuples read in from the file inputFiles/subset20.txt in Subtask 2b:

tuples20 = readCourseTuplesFromFile('inputFiles/subset20.txt')

In []: tuples20
Out[]: 
[('AFR 213', '01', 11, ['SBA']),
 ('AFR 243', '01', 21, ['ARS', 'REP']),
 ('AFR 295', '01', 20, ['HS', 'LL']),
 ('ASTR 101', '01', 36, ['MM', 'NPS']),
 ('ASTR 101', '02', 35, ['MM', 'NPS']),
 ('CHEM 102', '01', 24, ['NPS']),
 ('CHEM 105', '01', 11, ['MM', 'NPS']),
 ('CHEM 205', '01', 25, ['MM', 'QRF', 'NPS']),
 ('CHIN 223', '01', 14, ['HS', 'LL']),
 ('CHIN 231', '01', 12, ['EC', 'LL']),
 ('CS 111', '01', 22, ['MM']),
 ('CS 111', '02', 20, ['MM']),
 ('CS 111', '03', 17, ['MM']),
 ('CS 111', '04', 17, ['MM']),
 ('CS 349', '02', 32, ['MM', 'EC']),
 ('ECON 103', '01', 32, ['QRF', 'SBA']),
 ('ECON 103', '02', 23, ['QRF', 'SBA']),
 ('ECON 103', '03', 19, ['QRF', 'SBA']),
 ('ENG 112', '01', 31, ['ARS', 'LL']),
 ('ENG 295', '01', 20, ['HS', 'LL'])]

Most courses have only one section. E.g., 'AFR 213' has only one section with 11 students. But a few courses have multiple sections:

Here is the dictionary that groupCoursesByDistribution should generate for the input tuples20:

In []: groupCoursesByDistribution(tuples20)
Out[]: 
{'ARS': {'AFR 243': 21, 'ENG 112': 31},
 'EC': {'CHIN 231': 12, 'CS 349': 32},
 'HS': {'AFR 295': 20, 'CHIN 223': 14, 'ENG 295': 20},
 'LL': {'AFR 295': 20,
  'CHIN 223': 14,
  'CHIN 231': 12,
  'ENG 112': 31,
  'ENG 295': 20},
 'MM': {'ASTR 101': 71,
  'CHEM 105': 11,
  'CHEM 205': 25,
  'CS 111': 76,
  'CS 349': 32},
 'NPS': {'ASTR 101': 71, 'CHEM 102': 24, 'CHEM 105': 11, 'CHEM 205': 25},
 'QRF': {'CHEM 205': 25, 'ECON 103': 74},
 'REP': {'AFR 243': 21},
 'SBA': {'AFR 213': 11, 'ECON 103': 74}}

Note how courses with multiple sections have been collapsed into a single course in the course subdictionaries.

Notes:

Subtask d: Write Python data to a JSON file

In this subtask, you will write a Python data value (possibly involving nested dictionaries and lists) to a JSON file.

Define a function writePythonDataToJSONFile that takes two arguments --- (1) a Python value and (2) the name of a file --- and writes a nicely formatted version of the JSON representation of the value to the given file.

For example, executing the following two lines

In []: distDict20 = groupCoursesByDistribution(tuples20)

In []: writePythonDataToJSONFile(distDict20, 'outputFiles/distDict20.json')

should create a file in the outputFiles folder named distDict20.json with the following contents:

{
  "ARS": {
    "AFR 243": 21,
    "ENG 112": 31
  },
  "EC": {
    "CHIN 231": 12,
    "CS 349": 32
  },
  "HS": {
    "AFR 295": 20,
    "CHIN 223": 14,
    "ENG 295": 20
  },
  "LL": {
    "AFR 295": 20,
    "CHIN 223": 14,
    "CHIN 231": 12,
    "ENG 112": 31,
    "ENG 295": 20
  },
  "MM": {
    "ASTR 101": 71,
    "CHEM 105": 11,
    "CHEM 205": 25,
    "CS 111": 76,
    "CS 349": 32
  },
  "NPS": {
    "ASTR 101": 71,
    "CHEM 102": 24,
    "CHEM 105": 11,
    "CHEM 205": 25
  },
  "QRF": {
    "CHEM 205": 25,
    "ECON 103": 74
  },
  "REP": {
    "AFR 243": 21
  },
  "SBA": {
    "AFR 213": 11,
    "ECON 103": 74
  }
}

Notes:

    sort_keys=True, indent=2, separators=(',', ': ')

Without these keyword arguments, json.dump and json.dumps will put all data on one line. For example:

    In []: print(json.dumps(distDict20['LL']))
    {"ENG 295": 20, "ENG 112": 31, "CHIN 231": 12, "AFR 295": 20, "CHIN 223": 14}

    In []: print(json.dumps(distDict20['LL'], sort_keys=True, indent=2, separators=(',', ': ')))
    {
      "AFR 295": 20,
      "CHIN 223": 14,
      "CHIN 231": 12,
      "ENG 112": 31,
      "ENG 295": 20
    }

Subtask e: Read Python data from a JSON file

In this subtask, you will do the inverse of Subtask 2d, and define a function readPythonDataFromJSONFile that takes as its single argument the name of a file containing JSON data and returns a Python data value that results from reading that file.

For example:

In []: newDistDict20 = readPythonDataFromJSONFile('outputFiles/distDict20.json')

In []: distDict20 == newDistDict20
Out[]: True

In []: distDict20['ARS']
Out[]: {'AFR 243': 21, 'ENG 112': 31}

In []: newDistDict20['ARS']
Out[]: {u'AFR 243': 21, u'ENG 112': 31}

In []: distDict20['ARS'] == newDistDict20['ARS']
Out[]: True

In []: 'AFR 243' == u'AFR 243'
Out[]: True

Notes:

SubTask f: Group distribution courses by department

In this subtask you will define a function groupDistributionCoursesByDepartment that takes as its single argument the name of a file containing the JSON representation of the kind of distribution dictionary you created in Subtask 2c and wrote out to a JSON file in Subtask 2e. It returns a new dictionary in which the key are again the distribution codes and the values are subdictionaries, but this time the subdictionaries (which we'll call department subdictionaries) are different:

Additionally, the pairs should be sorted in descending by enrollment. If two pairs have the same enrollment, the one with the higher course number should come first. For example, here is the ordering of pairs in the list associated with the 'SPAN' department in full Spring 17 datatset:

    [('202', 74), ('241', 22), ('307', 17), ('243',  16), ('242', 16), 
     ('265', 15), ('275', 14), ('271', 14), ('324', 11), ('301', 11), 
     ('268', 8), ('302', 7), ('256', 6)]

To remove the dependency between this Subtask 2f and Subtasks 2c and 2d, we will use correct JSON files for distribution dictionaries that we have put in the inputFiles folder:

    groupCoursesByDistribution(readCourseTuplesFromFile('inputFiles/subset20.txt'))
    groupCoursesByDistribution(readCourseTuplesFromFile('inputFiles/subset50.txt'))
    groupCoursesByDistribution(readCourseTuplesFromFile('inputFiles/spring17courses.txt'))

Here's an example of groupDistributionCoursesByDepartment in action

In []: groupDistributionCoursesByDepartment('inputFiles/distDict50Soln.json')
Out[]: 
{u'ARS': {u'AFR': [(u'243', 21)],
  u'AMST': [(u'117', 49)],
  u'ARTH': [(u'100', 15)],
  u'ARTS': [(u'105', 16)],
  u'CAMS': [(u'101', 26), (u'234', 12)],
  u'CLCV': [(u'206', 17)],
  u'CPLT': [(u'200', 16)],
  u'EALC': [(u'253', 12)],
  u'ENG': [(u'112', 31)]},
 u'EC': {u'BISC': [(u'306', 13)],
  u'CHIN': [(u'231', 12)],
  u'CS': [(u'349', 32)],
  u'EDUC': [(u'305', 8)]},
 u'HS': {u'AFR': [(u'295', 20)],
  u'CHIN': [(u'223', 14)],
  u'CLCV': [(u'206', 17), (u'106', 17)],
  u'CPLT': [(u'208', 15)],
  u'ENG': [(u'295', 20)]},
 u'LL': {u'AFR': [(u'295', 20), (u'320', 7)],
  u'AMST': [(u'117', 49), (u'228', 13), (u'365', 5)],
  u'CAMS': [(u'234', 12)],
  u'CHIN': [(u'202', 15), (u'223', 14), (u'231', 12)],
  u'CLCV': [(u'213', 20)],
  u'CPLT': [(u'200', 16), (u'359', 6)],
  u'EALC': [(u'253', 12), (u'221', 9)],
  u'ENG': [(u'113', 35), (u'112', 31), (u'295', 20)],
  u'WRIT': [(u'390', 11)]},
 u'MM': {u'ASTR': [(u'101', 71)],
  u'BIOC': [(u'331', 18)],
  u'CHEM': [(u'205', 25), (u'105', 11)],
  u'CS': [(u'111', 76), (u'349', 32)]},
 u'NPS': {u'ASTR': [(u'101', 71), (u'203', 18)],
  u'BIOC': [(u'220', 49), (u'331', 18)],
  u'BISC': [(u'110', 30), (u'111', 22), (u'306', 13)],
  u'CHEM': [(u'205', 25), (u'102', 24), (u'105', 11)]},
 u'QRF': {u'BISC': [(u'111', 22)],
  u'CHEM': [(u'205', 25)],
  u'ECON': [(u'103', 74)]},
 u'REP': {u'AFR': [(u'243', 21)],
  u'AMST': [(u'228', 13)],
  u'CPLT': [(u'208', 15)]},
 u'SBA': {u'AFR': [(u'213', 11)],
  u'CAMS': [(u'232', 10)],
  u'ECON': [(u'103', 74), (u'101', 29)],
  u'EDUC': [(u'303', 7)],
  u'WRIT': [(u'390', 11)]},
 u'W': {u'WRIT': [(u'180', 16)]}}

Notes:

Subtask g: Creating sorted tuples from department subdictionaries

In this subtask you will define a function deptSubdictToSortedTuples that converts a single department subdictionary into a list of tuples satisfying certain conditions.

As an example of a single department subdictionary, consider the department subdictionary for the 'LL' distribution code in the distribution/department dictionary created from 'inputFiles/distDict50Soln.json':

In []: distDeptDict50 = groupDistributionCoursesByDepartment('inputFiles/distDict50Soln.json')

In []: LL50 = distDeptDict50['LL']

In []: LL50
Out[50]: 
{u'AFR': [(u'295', 20), (u'320', 7)],
 u'AMST': [(u'117', 49), (u'228', 13), (u'365', 5)],
 u'CAMS': [(u'234', 12)],
 u'CHIN': [(u'202', 15), (u'223', 14), (u'231', 12)],
 u'CLCV': [(u'213', 20)],
 u'CPLT': [(u'200', 16), (u'359', 6)],
 u'EALC': [(u'253', 12), (u'221', 9)],
 u'ENG': [(u'113', 35), (u'112', 31), (u'295', 20)],
 u'WRIT': [(u'390', 11)]}

The deptSubdictToSortedTuples function takes a department subdictionary like LL50 and returns a list of quadruples (4-tuples), each of which has the following components:

  1. The name of the department, such as 'AFR' or 'AMST'.

  2. The number of courses in the department within the subdictionary. In the above example, there is 1 course for 'CAMS', 2 courses for 'AFR', and 3 courses for 'CHIN'.

  3. The total enrollment in courses in the department within the subdictionary. In the above example, there are 12 students enrolled in 'CAMS', 20 + 7 = 27 students enrolled in 'AFR' courses, and 15 + 14 + 12 = 41 studens enrolled in 'CHIN' courses,

  4. The sorted list of courseNumber/enrollment pairs associated with the department in the subdictionary. For example, for 'CHIN', this is [(u'202', 15), (u'223', 14), (u'231', 12)].

Furthermore, the tuples are sorted as follows:

For example:

In [51]: deptSubdictToSortedTuples(LL50)
Out[51]: 
[(u'ENG', 3, 86, [(u'113', 35), (u'112', 31), (u'295', 20)]),
 (u'AMST', 3, 67, [(u'117', 49), (u'228', 13), (u'365', 5)]),
 (u'CHIN', 3, 41, [(u'202', 15), (u'223', 14), (u'231', 12)]),
 (u'AFR', 2, 27, [(u'295', 20), (u'320', 7)]),
 (u'CPLT', 2, 22, [(u'200', 16), (u'359', 6)]),
 (u'EALC', 2, 21, [(u'253', 12), (u'221', 9)]),
 (u'CLCV', 1, 20, [(u'213', 20)]),
 (u'CAMS', 1, 12, [(u'234', 12)]),
 (u'WRIT', 1, 11, [(u'390', 11)])]

Notes:

Subtask h: Write course info for a given distribution to a file

In this final subtask, you will define a function writeCoursesToFile that takes two arguments: (1) a distribution code (as a string) and (2) a distribution/department dictionary of the kind created in Subtask 2f. It does not return a result. Instead, it creates a file in the outputFiles directory with the name of the distribution code and a .txt extension (e.g., 'MM.txt', 'ARS.txt').

For example, the following two statements will create a file named outputFiles/LL.txt:

In []: distDeptDictSpring17 = groupDistributionCoursesByDepartment('inputFiles/distDictSpring17Soln.json')

In []: writeCoursesToFile('LL', distDeptDictSpring17)

Here's what the contents of outputFiles/LL.txt should look like:

LL courses sorted by department offerings:

ENG (29 courses with 469 total students enrolled):
117 (49)
113 (35)
112 (31)
247 (30)
120 (29)
203 (23)
295 (20)
277 (18)
254 (17)
224 (17)
355 (15)
325 (15)
273 (15)
291 (14)
213 (13)
206 (13)
204 (12)
390 (11)
302 (11)
345 (10)
301 (10)
283 (10)
234 (10)
387 (9)
289 (9)
262 (7)
251 (7)
365 (5)
381 (4)

SPAN (13 courses with 231 total students enrolled):
202 (74)
241 (22)
307 (17)
243 (16)
242 (16)
265 (15)
275 (14)
271 (14)
324 (11)
301 (11)
268 (8)
302 (7)
256 (6)

CHIN (13 courses with 161 total students enrolled):
202 (30)
204 (16)
382 (14)
323 (14)
223 (14)
338 (12)
331 (12)
231 (12)
302 (9)
345 (8)
245 (8)
313 (6)
213 (6)

FREN (12 courses with 163 total students enrolled):
202 (42)
231 (17)
203 (16)
303 (12)
225 (12)
211 (12)
207 (12)
226 (10)
206 (9)
210 (8)
356 (7)
359 (6)

AMST (7 courses with 113 total students enrolled):
117 (49)
231 (17)
240 (15)
228 (13)
320 (7)
212 (7)
365 (5)

RUSS (5 courses with 49 total students enrolled):
277 (22)
202 (13)
276 (6)
376 (4)
302 (4)

JPN (5 courses with 46 total students enrolled):
202 (19)
290 (12)
232 (6)
314 (5)
353 (4)

GER (4 courses with 45 total students enrolled):
286 (25)
239 (8)
202 (7)
389 (5)

ITAS (4 courses with 30 total students enrolled):
203 (9)
320 (8)
202 (8)
274 (5)

WGST (3 courses with 82 total students enrolled):
120 (52)
210 (16)
326 (14)

ARAB (3 courses with 39 total students enrolled):
202 (24)
210 (11)
307 (4)

EALC (3 courses with 34 total students enrolled):
345 (13)
253 (12)
221 (9)

CLCV (2 courses with 40 total students enrolled):
313 (20)
213 (20)

CAMS (2 courses with 37 total students enrolled):
286 (25)
234 (12)

AFR (2 courses with 27 total students enrolled):
295 (20)
320 (7)

LAT (2 courses with 26 total students enrolled):
201 (15)
315 (11)

WRIT (2 courses with 24 total students enrolled):
105 (13)
390 (11)

CPLT (2 courses with 22 total students enrolled):
200 (16)
359 (6)

KOR (2 courses with 19 total students enrolled):
202 (10)
232 (9)

THST (2 courses with 16 total students enrolled):
253 (12)
353 (4)

ME/R (1 course with 30 total students enrolled):
247 (30)

REL (1 course with 13 total students enrolled):
220 (13)

PORT (1 course with 9 total students enrolled):
203 (9)

HNUR (1 course with 8 total students enrolled):
202 (8)

SWA (1 course with 7 total students enrolled):
202 (7)

HEBR (1 course with 7 total students enrolled):
202 (7)

GRK (1 course with 4 total students enrolled):
307 (4)

Observe the following

Notes:


Honor Code Form and Final Checks

  1. As in the previous psets, your honor code submission for this pset will involve defining entering values for the variables in the honorcode.py file.

  2. If you wrote any function invocations or print statements in your Python files to test your code, please remove them, comment them out before you submit, or wrap them in a if __name__=='__main__' block at the end of a .py file. Points will be deducted for isolated function invocations or superfluous print statements.

  3. Run Codder one last time on Task 2 (Partner-recommended task)

How to turn in this Problem Set

Hardcopy (paper) submission

Softcopy (electronic) submission