06: Extra Project 1: Grade Calculator

By now you’re all pretty familiar with Python. We’ve seen how it can be used as a calculator, how it can handle large sets of information with the help of lists and FOR loops, but there’s still so much more we can do with Python (as well as other languages). You will notice the labs begin to add these elements together, so here’s a little DIY project to follow along and get a hang of everything you’ve learned so far.

I highly recommend you attempt to write the code first. Only refer to my solution code to verify that you are correct.

Introduction

Keeping track of your grades is important in University. Calculating them by hand can be confusing, and it is easy to make a mistake. We’re going to build a small script that will calculate your average grade based on the courses you enter in a .txt file.

Before you start, you should already be familiar with:
Lists
The FOR loop
Handling Files and File I/O
• String Manipulation

Let’s get started!

Theory

McMaster uses the 12 point grade scale. That means any grade you receive will be an integer between 0 and 12, inclusive. Your average, however, will often be a float. The formula for calculating our average is:

average = \frac{\sum_{i=1}^{n} grade_{i} \cdot units_{i}}{totalUnits}

Basically, we multiply each grade we receive with the number of units in that course, and then sum them. We take that sum and divide by the total number of units we’ve taken so far. Let’s get started in Python.

The Code

File: grades.txt

We are entering our grades through a .txt file. To make sure we are able to get all the information we need, let’s stick to a very strict format. Here’s what my file looks like. Change the contents as you wish to suit your schedule. The number of courses doesn’t matter, but you must make sure they follow the same format.

NOTE: All programs, courses, and grades are separated by a tab.

DEPARTMENT Course Grade 

CHEM    1E03   5
ENG     1D04   10 
ENG     1P03   8 
ENG     1C03   9 
MATLS   1M03   7 
MATH    1ZA3   8 
MATH    1ZB3   4 
MATH    1ZC3   6 
PHYS    1D03   10 
PHYS    1E03   7 
PSYCH   1X03   11 
PSYCH   1XX3   9

File: gradeCalculator.py

Go ahead and open up IDLE. Make a new file by pressing ctrl+n (cmd+n on Mac OSX). Save this file in the same directory as your .txt file from before.

This file is going to do all the calculation and print our results. The instructions to build the file are below. Follow along and try your best to write the code. If you get stuck, try adding a few print statements to see what your variables are doing. If you’re really stuck, refer to my code hidden below each section. When you’re done, you can save the file as a .exe so it will run immediately and search for the grades.txt file in its directory.

Good luck!


Step 1: Set up .py File

For good practice, let’s describe the file in comments at the top. Here’s what mine looks like:

# File: gradeCalculator.py 
# Author: Jamie Counsell (copyright 2013) 
# 
# 1D04 Extra Project 1 
# 

# This program computes the sessional 
# average of marks given in a .txt file. 
#

Step 2: Getting The File

First, we need to open the file, save its contents to a variable to be used later, and close it. Go ahead and code that now. When you’re done, come back and have a look at mine. Remember to use only the read permission ‘r’ to avoid accidentally overwriting or erasing the file. (More on File I/O)

• Open the file using Python’s open(file, mode) function.
• Store the contents to a variable content
• which “read” function is the best to use here? read, readline, or readlines? (More on read functions here)

Before you continue, verify you’re reading the content correctly by printing your content variable to the Shell.

# Open our file from the current directory 
f = open("grades.txt", "r") 

# Read its content and seperate into a list of lines. 
# Each line is an element in the resulting list. 
content = f.readlines() 

# Close the file 
f.close()
Step 3: Splitting it Up

Next, we have to split the content up so it’s easier to work with. We want to separate programs and course codes from their units and grades. Python has a great String method called split() that can help with this. split(), when given no argument, splits a string into a list by separating the string at whitespace. This includes spaces, tabs, and new lines.

We know that the readlines() function gave us a list of strings, where each string is one line. By splitting up these lines, we will get a list of smaller lists, containing all the elements we need.

Example of split():

string = "Python is the best." 
print string.split()

We see the output is:

['Python', 'is', 'the', 'best.']

If we apply this to our lines, we can separate program, course code, and mark into their own elements. This is best done inside a FOR loop of the following format:

for i in range( len(content) ): 
    content[i] = content[i].split()

This will iterate through each element in content (in this case, each element is one line from our file), splitting it and saving the new value (a list separated by whitespace) back to its original spot.

# This loop splits the lines by whitespace (Tabs) 
# Note the range. Why is this correct? 
for i in range(len(content)): 
    # Split the current line by whitespace and 
    # save it back into the list 
    content[i] = content[i].split()
Step 4: Removing Junk

Looking at our example file, we know the first two lines do not contain information we need to process. It is best to get rid of these lines before going any further, as attempting to perform calculations on the empty line could cause an error. Luckily, Python has a string method called pop(x) that removes the value located at index “x” of a list, and shifts all other elements to fill the gap.

To remove the first line, we need to remove the first element of the content list. Then, the second element shifts into its place. We want to remove that second element as well. How can we do this using the pop() method? (Hint: list.pop(3) removes the 4th item in list. The fifth item then shifts into its place, and so on.)

# Remove first element (header) 
# All elements shift down 
content.pop(0) 
# Remove first element again (blank line) 
# All elements shift down 
content.pop(0)
Step 5: The Math

To get the number of units in each course, we will utilize a property of the String type. Recall from studying strings that the string “abcdefg” can be referenced by index, just like a list. We see that:

if x = “abcdefg”  
   x[0]: a  
   x[1]: b  

and so on. Also recall that we can take a slice of the string, such as x[0:2] which will give us “ab”. Remember, like the range function, this includes the lower limit (0), but not the upper limit (2).

Using these methods, how can we determine the number of units that a course is worth, given the course code (ex. 1D04). We know that McMaster course codes have three sections:

  1. The first number indicates level (1D04 -> Level 1)
  2. The second two numbers indicate course (1D04 -> Course D0)
  3. The last number indicates the weight of the course in units (1D04 -> 4 units)

From this, we can see that we need to pull the last character off the course codes, and turn it into a number for our calculations? What type of number should we use? How do we turn a string into this type of number? (Hint: int(‘5′) and float(‘5′) return the integer and float values of the number 5. Which one is better?)

Our calculations should also be done in a FOR loop. This loop can iterate through each line, adding up all our grades and all the units of our courses. Remember that the calculation requires:

\sum_{i=1}^{n} grade_{i} \cdot units_{i}

So it’s best to declare these sums before the loop, such as:

# Initialize our sums 
totalCredit = 0 
totalUnits = 0

Where totalUnits is the sum of all course units, and totalCredit is the sum of [grade earned times units] for each course.

Use a loop to access all these numbers and perform the correct mathematical operation on them. When you’re done, remember that your average comes from the sums declared above, and is:

average = \frac{\sum_{i=1}^{n} grade_{i} \cdot units_{i}}{totalUnits}

Print your average to see if you’re correct! If you get stuck, take a look at my code below. When you finish, check with my code or calculate by hand to make sure your average is accurate.

# Remember this FOR loop structure from my tutorial 
# on FOR loops. we need to iterate through each element in 
# the list "content", and temporarily store this element 
# to the variable "course". In this case, "course" will 
# be equal a line from our file (course name, code, and grade) 
for course in content: 
    # The course code is the second element 
    # in the list (at index 1) 
    courseCode = course[1] 

    # The units are the 4th character of 
    # the course code 
    # We need its float value (for division) 
    units = float(courseCode[3]) 

    # The grade is the third element in the list. 
    # We need its float value (for division) 
    grade = float(course[2]) 

    # Let's now update our sums 
    totalCredit += grade * units 
    totalUnits += units
Step 6: Printing Our Results

Now let’s print some stuff! I stored my average as “average”. I also know my number of courses is the same as the number of elements in my “content” list (one course per line in my file), and I know the total number of units is stored as the sum “totalUnits”.

average = totalCredit/totalUnits 

print "Number of Courses: ", len(content) 
print "Total Units: ", totalUnits 
print "Sessional Average: ", average
Final Source Code

You can check out the final code below. Make sure you write your own code first, though!

# File: gradeCalculator.py 
# Author: Jamie Counsell
# 1D04 Extra Project 1 
# This program computes the sessional 
# average of marks given in a .txt file. 
# 

# Open our file from the current directory 
f = open("grades.txt", "r") 
# Read its content and seperate into a list of lines 
content = f.readlines() 
# Close the file 
f.close() 

# This loop splits the lines by whitespace (Tabs) 
# Note the range. Why is this correct? 
for i in range(len(content)): 
    # Split the current line by whitespace and 
    # save it back into the list 
    content[i] = content[i].split() 

# Remove first element (header) 
# All elements shift down 
content.pop(0) 

# Remove first element again (blank line) 
# All elements shift down 
content.pop(0) 

#Initialize our sums 
totalCredit = 0 
totalUnits = 0 

# Remember this FOR loop structure from my tutorial 
# on FOR loops. This iterates through each element in 
# the list "content", and temporarily stores this element 
# to the variable "course". In this case, "course" will 
# be equal a line from our file (course name, code, and grade) 
for course in content: 
    # The course code is the second element 
    # in the list (at index 1) 
    courseCode = course[1] 
    # The units are the 4th character of 
    # the course code # We need its float value (for division) 
    units = float(courseCode[3]) 
    # The grade is the third element in the list. 
    # We need its float value (for division) 
    grade = float(course[2]) 
    # Let's now update our sums 
    totalCredit += grade * units 
    totalUnits += units 

average = totalCredit/totalUnits 
print "Number of Courses: ", len(content) 
print "Total Units: ", totalUnits 
print "Sessional Average: ", average 

# End of File