• Practice writing and using classes in Python
  • Practice writing and using generator functions in Python
  • Practice writing and using higher-order functions in Python
  • Gain familiarity with some notable Python Standard Library modules
  • Use Python’s documentation to write code using unfamiliar modules

Relevant Resources

This assignment will require you to use modules from the Python Standard Library. There are links below to some resources, but feel free to search for other resources online or ask your instructors.

In addition to using code from the standard library, you will be required to implement code according to a given specification. Your class(es) and function(s) must adhere to the documentation available here.

There’s also sample data here.

Awkward Situation Avoid-inator

You receive a voicemail from Dr. Doofenshmirtz. His voice is filled with frustration:

“I can’t believe this! Norm took Gug for a walk, then Gug turned to Norm, said something about learning to be yourself, pulled out an umbrella, and just flew away into the sky. That’s not fair! Norm got solid life advice after one walk. I mean, I spent weeks feeding and walking and singing and all I got was an old banana peel in my shoe.”

Our Story Continues

Sitting at your new desk, you hear your office door open and slam shut. Dr. Doofenshmirtz is kneeling in your office looking out through the crack beneath the door. After some time, he stands and faces you.

“I hate these situations” he explains as he brushes dust off his lab coat. “I hired some temps to run some evil errands. We got them all name tags, so that I could get to know them. Then they… well…” There’s a loud laughing sound and unintelligible chanting happening on the other side of the door.

What even is happening here?

“They made this game where they stand in a circle and close their eyes and… well the rules aren’t important. The game involves trading name tags. They think it’s hilarious, but I don’t really get it. I tried switching nametags with Norm, but it wasn’t really that funny. I even put his upside down and it wasn’t very funny.”

“Anyway… Now their name tags are all switched, and I’m too embarrassed to ask them their names at this point. If I’m being honest, I’m having a hard time telling them apart.”

“Luckily, the temp agency left us this GPS tracker thingy and, long story short, we can tell when two of them traded name tags. I am not going to embarrass myself out there anymore, so I need a program that tells me their names. I offered them some fruit earlier and it did not go over well. Even though it’s all they talk about, they don’t seem to actually like bananas.”

Name tag Switcheroo

Long story short, the minions made a game of swapping name tags, and Dr. Doofenschmirtz can’t tell what their names actually are anymore. Now it’s your job to figure out who is who.

Dr. Doofenshmirtz has a super-accurate GPS doodad that tracks minions by employee ID number. Dr. D already asked Norm to walk up to each minion and determine its ID, so now we have a file that tell us which minion ID maps to which (probably incorrect) name tag.

Additionally, Dr. Doofenshmirtz figured out how to get the GPS doodad to output swap data. It turns out to be pretty straightforward: if two minions were close enough to swap name tags, they swapped name tags.

So here’s the data we have:

Swap data
Describes at what time two minions swapped name tags, identifying minions by employee ID
Final name tag mapping
Maps a minion’s employee ID to the name tag it is currently wearing. This is the result of playing that ridiculous game. It is likely that most minions are not wearing the correct nametag.

File Formats

Swap Data

Swap data is encoded in a straightforward manner:

ID1 swapped with ID2 at TIME


is the first minion’s employee ID number
is the second minion’s employee ID number
is the timestamp of the swap

There are a few things to note about the swap data:

  • Every swap event is on its own line.
  • The lines of the file are not sorted in any way.
  • The time is formatted as %Y-%m-%dT%H:%M:%S
  • Employee IDs are integers.
    • An ID may be more than one digit long.
  • Each word in the line is separated by a single space character.
    • You can assume every line will have exactly 6 words.
    • It is safe to split the words using the str.split() method
  • Sample data is available here

Initial Name Tag State

The initial mapping of minion IDs to names is formatted as a JSON object.

  "0": "Joshua",
  "1": "Scott",
  "2": "Dylan"

Do not try to parse this yourself. You should use Python’s json module to serialize or deserialize data to or from JSON strings.

Your Task

Your task is to complete a tool for Dr. Doofenshmirtz that can tell him the minions’ real names. The tool requires two data files as input and outputs a JSON object that maps minion IDs to their real names as a result.

You are to do the following:

  • Complete the partially implemented program.
  • Complete the partially implemented test functions.

Refer to this assignment’s rubric for further description of point values for completed tasks.

Your Program’s User Interface

In this assignment, your program will accept command line options and parameters:

  • --id: An optional flag that allows the user to specify a minion, so that they only see the name of that minion
    • It requires that you provide an ID
    • Example: --id=54
  • swap_file: A required positional argument. The file path to the swap data file we’re reading.
  • state_file: A required positional argument. The file path to the initial id/name JSON data.

The starter code already handles the command line argument parsing for you. It uses the argparse Python module. Though the code for the argument parsing is already complete, you should review it because argparse is extremely useful.

$ python3.5 main.py
usage: main.py [-h] [--id ID] swap_file state_file
main.py: error: the following arguments are required: swap_file, state_file

You can read more about the arguments by passing the --help flag.

$ python3.5 main.py --help
usage: main.py [-h] [--id ID] swap_file state_file

positional arguments:
  swap_file   A file containing swap data
  state_file  A file containing nametag state

optional arguments:
  -h, --help  show this help message and exit
  --id ID     Output the name of the minion with this ID

The help screen shows that the tool expects two required positional arguments: the swap data file and the initial state file. If we run the tool with a two data files, we get a nicely formatted JSON object explaining who is who.

# Cut the output short here. There is example output elsewhere.
$ python3.5 main.py data/5/10/swaps.txt data/5/10/initial.json
{"4": "Dylan", "0": "Scott", "2": "Joshua", "1": "Russell", "3": "Harry"}

If we use the --id flag, we can specify a minion ID and determine what their name is.

$ python3.5 main.py --id=0 data/5/10/swaps.txt data/5/10/initial.json
0: Scott

Error Handling

Do Handle

Your program must gracefully handle the following errors. None of these situations should produce a backtrace. Instead, we expect to see nicely formatted messages for the user. The messages are yours to write. Make them short but informative.

  • Any error thrown by attempting to open a bogus file
    • You’ll have to modify main() by adding exception handling logic.
    • Print a helpful message to the user
  • Non-existent minion IDs passed via --id

Do Not Handle

Your program does not have to handle the following errors. Just let the backtrace come through.

  • Incorrectly formatted swap data.
    • You can assume it’s formatted correctly.
  • Incorrectly formatted initial JSON data.
    • You can assume it’s formatted correctly.

Program Design

Your implementation must contain classes and functions in the modules as described in the module documentation.

If you fail to follow the module docs, your code won’t work with our unit tests. If your code doesn’t work with our unit tests, you will lose points.

Automated Tests

You are responsible for ensuring that your functions and classes work as described. A few tests have been written in

  • test_unswap.py
  • test_swapevent.py

You are encouraged to write additional tests and create additional test files as necessary. Be sure to document any additional tests that you write.

Remember that pytest is picky about how you name your test functions. They must be prefixed with test_ for them to run. Also, feel free to make use of pytest.xfail as you write your tests.

For this assignment, none of your tests should fail. If you know that a test will fail, use pytest.xfail() to indicate to us that you’re expecting the test to fail.

Using pytest

Once again, we’ve included a py.test script that will grab the right version of pytest and run it for you. You can invoke it in the same way as before:

# Assuming we're in the same directory as the py.test executable and our
# test scripts, we can run py.test like this to run all of the tests in
# the current directory

The script uses the following version of pytest. This is the exact version that we will use to grade your assignment.

$ ./py.test --version
This is pytest version 3.2.1 ...

Proper Python Style

We have included the same flake8 script in your starter code that was included for homework 2. It can be started the same way.

# Assuming we're in the same directory as the flake8 executable and our
# Python code, we can run flake8 like this to check the style of all the
# .py files in the current directory
$ ./flake8 *.py

Make sure that flake8 has no output when you pass it your files. All of the .py files should pass flake8’s checks. If there are any messages (warnings, errors, etc), you should address them. Do not suppress or exclude any of flake8’s rules.

Using flake8

The script installs the following version of flake8, which is exactly what we will use to grade your assignment.

$ ./flake8 --version
3.4.1 (mccabe: 0.6.1, pycodestyle: 2.3.1, pyflakes: 1.5.0) ...

Further Details

Important Notes

  • Ensure your program’s output is what we expect.
    • Refer to the documentation and above examples for formatting requirements.
    • You’re on your own to write error messages. Those are up to you.
  • Your function/class/method implementations must adhere to the provided documentation.
  • You will need to create swapevents.py on your own. Make sure you add it to your Git repository and check GitLab to make sure it’s there when you’re ready to submit.
  • Note that if you don’t follow the design specifications for this assignment, your grade will suffer.
  • You’ll need to remove the pytest.mark.skip decorators from the existing unit tests. They’re decorated in the starter code to make it easier for you to work with the tests one-at-a-time.
  • In the end, we expect all of your your unit tests to pass. You shouldn’t fail, xfail, skip, etc. any of your tests.
  • Don’t forget to address all TODO items in the code. Some of them require you to explain how the starter code works!
  • Don’t commit any data files (.json or .txt) or any byte-compiled files (.pyc or anything in __pycache__/). We don’t want that junk cluttering up your repositories.

Tips and Hints

  • The order of the keys in the output JSON object does not matter.
  • If you’re exiting the program due to an error, you should consider using the sys.exit() function. sys.exit() accepts a message to print and kills your program. It also causes your program to return a non-zero exit status to indicate the program encountered an error.
  • You can use the isinstance() function to determine whether an object is an instance of a class.

Point Value

This assignment is worth 100 points. It will be graded according to the following rubric:

Feature Points Possible Mostly or completely incorrect (0% of points possible) Needs improvement (50% of points possible) Adequate, but still some deficiencies (75% of points possible) Mostly or completely correct (100% of points possible)
Unit Testing          
Student tests are thorough and pass (no xfail, skip, fail, etc.) 5 -5 -3 -2 0
Student passes instructor tests for swapevent module 10 -10 -5 -3 0
Student passes instructor tests for items in main module 10 -10 -5 -3 0
Demonstrates understanding of modules and importing 5 -5 -3 -2 0
Demonstrates understanding of classes (member variables/functions, doc strings, special methods, etc.) 10 -10 -5 -3 0
Demonstrates understanding of generator functions 5 -5 -3 -2 0
Demonstrates understanding of datetime module 3 -3 -2 -1 0
Demonstrates understanding of json module 2 -2 -1 0
Written Responses          
Correctly explains code behavior in body of unswap 10 -10 -5 -3 0
Program completes without runtime/syntax errors 5 -5 0
Program results match expected results from provided data files 5 -5 -3 -2 0
Program results match expected results from instructor data files (Hint: good unit tests will assist you in getting these points) 5 -5 -3 -2 0
Program output format matches expected output format 5 -5 -3 -2 0
flake8 outputs no suggestions for improvement for any “.py” file (no warnings, no errors, etc.) 10 -10 0
Code Review (sufficient comments, implementation looks Pythonic) 10 -10 -5 -3 0
Points possible 100        

Each cell indicates how many points out of the available points will be awarded for that feature (row) and assessment level (column).

Cells marked -- cannot be achieved.



  • 11:59:59 PM CDT – Friday, September 22, 2017

Submission Procedure

Refer to the assignment submission page on the course website for details on submitting your code to GitLab.

Grading Procedure

When your assignment is graded, the following (roughly) will be used.

# Run flake8 and check for style issues. Note versions above.
$ ./flake8 *.py

# Run unit tests. Also note versions above.
$ ./py.test

# Run your main file and check the output
$ python3.5 main.py swap_file.txt state_file.json

# Run your main file and check the output with different files
$ python3.5 main.py other_swap_file.txt other_state_file.json

# With the id flag
$ python3.5 main.py --id swap_file.txt state_file.json

# You get the idea. I'm going to try a BUNCH of different
# stuff to give your program a workout.