views:

197

answers:

7

I am currently writing a program that reads records from a txt file and prints the data on the screen as such:

                       GRADE REPORT

NAME                COURSE                            GRADE
-----------------------------------------------------------
JOE FRITZ           AMERICAN GOVERNMENT                 B
                    CALCULUS I                          A
                    COMPUTER PROGRAMMING                B
                    ENGLISH COMPOSITION                 A
                    Total courses taken = 4

LANE SMITH          FUND. OF DATA PROCESSING            B
                    INTERMEDIATE SWIMMING               A
                    INTRO. TO BUSINESS                  C
                    Total courses taken = 3

JOHN SPITZ          CHOIR                               C
                    COLLEGE STATISTICS                  B
                    ENGLISH LITERATURE                  D
                    INTRO. TO BUSINESS                  B
                    Total courses taken = 4

Total courses taken by all students = 11

Run complete.  Press the Enter key to exit.

This is the text file it reads from:

JOE FRITZ           AMERICAN GOVERNMENT           B
JOE FRITZ           CALCULUS I                    A
JOE FRITZ           COMPUTER PROGRAMMING          B
JOE FRITZ           ENGLISH COMPOSITION           A
LANE SMITH          FUND. OF DATA PROCESSING      B
LANE SMITH          INTERMEDIATE SWIMMING         A
LANE SMITH          INTRO. TO BUSINESS            C
JOHN SPITZ          CHOIR                         C
JOHN SPITZ          COLLEGE STATISTICS            B
JOHN SPITZ          ENGLISH LITERATURE            D
JOHN SPITZ          INTRO. TO BUSINESS            B

Here is my code:

# VARIABLE DEFINITIONS

name = ""
course = ""
grade = ""
recordCount = 0
eof = False
gradeFile = ""

#-----------------------------------------------------------------------
# CONSTANT DEFINITIONS

#-----------------------------------------------------------------------
# FUNCTION DEFINITIONS

def startUp():
    global gradeFile
    gradeFile = open("grades.txt","r")
    print ("grade report\n").center(60).upper()
    print "name".upper(),"course".rjust(22).upper(),"grade".rjust(32).upper()
    print "-" * 60
    readRecord()

def readRecord():
    global name, course, grade

    studentRecord = gradeFile.readline()
    if studentRecord == "":
        eof = True
    else:
        name = studentRecord[0:20]
        course = studentRecord[20:50]
        grade = studentRecord[50:51]
        eof = False

def processRecords():
    numOfRecs = 0
    while not eof:
        numOfRecs += 1
        printLine()
        readRecord()
    return numOfRecs

def printLine():
    print name, course.rjust(3), grade.rjust(3)

def closeUp():
    gradeFile.close()
    print "\nTotal courses taken by all students = ",recordCount

#-----------------------------------------------------------------------
# PROGRAM'S MAIN LOGIC

startUp()
recordCount = processRecords()
closeUp()

raw_input("\nRun complete. Press the Enter key to exit.")

The results just print the very last line of the txt file and is stuck in a loop. Any assistance would be greatly appreciated. Thank you for your time.

+2  A: 

In this design, "eof" needs to be addded to the globals list in readRecord()

Otherwise assigning it creates a new local variable, which processRecords() never sees.

Adam Vandenberg
Oh okay, now I see that. Thanks, a bunch, I've been scratching my head for a few hours on that one. Do you know how I should go about stripping the students names from each subsequent course printed?
eleven357
name = studentRecord[0:20].strip()
Adam Vandenberg
thanks man I appreciate it!
eleven357
+3  A: 

You have to declare eof as global in readRecord():

def readRecord():
    global eof, name, course, grade

Otherwise, the changes you make to eof when studentRecord is empty won't survive outside readRecord()'s scope .

Frédéric Hamidi
thank you for your help!
eleven357
+1  A: 

You're missing a global here, while your while loop checks the global variable eof, your readRecord function does in fact set the local variable eof.

Ivo Wetzel
thanks for the help
eleven357
+1  A: 

You need to add eof to the global variables in readRecord():

...
def readRecord():
    global name, course, grade, eof
...

But your solution is a bit un-pythonic. How about something shorter and more flexible:

import re
print ("grade report\n").center(60).upper()
print "name".upper(),"course".rjust(22).upper(),"grade".rjust(32).upper()
print "-" * 60
for line in open("grades.txt"):
    name, course, grade = re.split("   *", line.strip())
    print "%-21s%-34s%-21s" % (name, course, grade)

raw_input("\nRun complete. Press the Enter key to exit.")

The regular expression is a very simple one that splits on multiple spaces. If you delimiter is something else, then replace the regular expression " *" with your delimiter.

And here is a version that uses python dicts to track the courses and grades by student (i.e. your target output):

import re

print ("grade report\n").center(60).upper()
print "name".upper(),"course".rjust(22).upper(),"grade".rjust(32).upper()
print "-" * 60
grades = {}
total_courses = 0
for line in open("grades.txt"):
    name, course, grade = re.split("   *", line.strip())
    if not grades.get(name): grades[name] = []
    grades[name].append([course, grade])

for name, data in grades.items():
    for course, grade in data:
        print "%-21s%-34s%s" % (name, course, grade)
        name = ""
    print "%-21sTotal courses taken = %d\n" % (" ", len(data))
    total_courses += len(data)

print "Total courses taken by all students = %d" % total_courses

raw_input("\nRun complete. Press the Enter key to exit.")

BTW, it sounds like you need to learn more about python (and the python way of programming). I recommend Dive Into Python. IMO it's the fastest (and most entertaining) way to come up to speed in python if you are have some programming experience.

kanaka
Thanks. How would I go about taking the redundant names from each subsequent course name?
eleven357
I'm not sure I understand your comment, but assuming that it's related to your original goal, I've updated with another example that uses a python dict to store the info per person. Also, you should definitely read "Dive Into Python" which I've linked to as well.
kanaka
Thanks man, I do appreciate the help. This is my 8th program that I have written in Python, and I am still learning new things everyday. I hope to master it someday.
eleven357
+6  A: 

Why don't you do it all in a single function -

def processRecords():
    print ("grade report\n").center(60).upper()
    print "name".upper(),"course".rjust(22).upper(),"grade".rjust(32).upper()
    print "-" * 60

    rec_count = 0
    for line in open("grades.txt","r"):
        name   = line[0:20]
        course = line[20:50]
        grade  = line[50:51]
        print name, course.rjust(3), grade.rjust(3)
        rec_count += 1
    return rec_count

All those functions compressed in this one single function. You seem to be programming much like C code. This is Python!

Also try to avoid using globals unless you must. Just a principle I follow. Clearly in this situation you don't need to.

MovieYoda
THIS...IS...PYTHON!!!!!! ;)
CrazyJugglerDrummer
@crazyJug Sounds like a dialogue from 300, where he says - THIS IS SPARTA!!!! ;)
MovieYoda
Thank you for helping me out!
eleven357
@user[0-9]{6} anytime! :)
MovieYoda
My instructor doesn't want me to do it all in a single function per "Structured Programming" reasons. So I still have to use the functions I have, but thanks for opening my eyes to the possibilities. I also need to have a second counter to count the students individual courses. I am not sure if I should create another loop inside the first loop to count the individual courses taken. Is this right? Or am I not even close?
eleven357
yeah you can use a second counter to keep track of courses. In python you can return more than one value from a function. So you could do - `return (rec_count, course_count)`
MovieYoda
Thank you very much for helping me. That is great to know that it can return more than one value. Sorry to sound like a n00b, but where should I put the courseCounter at in my code to make it just count each students courses?
eleven357
put it the function where you are extracting your `courses` :) as per your code it would be `readRecord()`
MovieYoda
@user485786: before the loop: `student_course_count = defaultdict(int)`; in the loop: `student_course_count[name]+=1`; after the loop: `return rec_count, student_course_count`
J.F. Sebastian
thanks guys, I really do appreciate the help.
eleven357
How would I only print out the courseCount when it reached the end of a particular students courses, like say when it is done reading all the the courses that student 'A' took, print courseCount. Thanks and sorry if I am frustrating anyone.
eleven357
you could do what sabastian suggested
MovieYoda
I apologize for my n00bishness, if I add it to the printLine() function, won't it print it everytime? Do I need to do a comparison of the names before printing out their individual course Count?
eleven357
What about outputting than name only at the start of the student's records?
ΤΖΩΤΖΙΟΥ
This is a terrible idea. What if you change the source of the records? what if you change the way you display them? what if you decide to pre or post process them? One big function is almost always the wrong way to go. See ΤΖΩΤΖΙΟΥ`s answer.
aaronasterling
If your instructor would rather use globals over a simple function, I'd question his merit as an instructor. Instead of globals, consider passing parameters to the functions.
Josh Smeaton
+1  A: 

You have to add eof in the list of globals in readRecord.

However, you said any help, so here's another version:

import itertools as it, operator as op
import collections

Record= collections.namedtuple("Record", "name course grade")
grouper= op.itemgetter(0) # or op.attrgetter('name')

def file_reader(fobj_in):
    for line in fobj_in:
        name= line[:20].rstrip()
        course= line[20:50].rstrip()
        grade= line[50:].rstrip()
        yield Record(name, course, grade)

def process(fn_in, fobj_out):
    for name, records in it.groupby(file_reader(fobj_in), grouper):
        out_name= name
        for index, record in enumerate(records, 1):
            fobj_out.write(
                "%-20.19s%-36.35s%s\n" % (out_name, record.course, record.grade)
            )
            out_name= ''
        fobj_out.write("%20sTotal courses taken = %d\n" % ('', index))

if __name__ == "__main__":
    import sys

    with open('so4009899.txt', 'r') as fobj_in:
        process(fobj_in, sys.stdout)
ΤΖΩΤΖΙΟΥ
A: 

If you have to use many functions for the purposes of 'structure', consider passing parameters to the functions instead of using globals. Here is a small change that illustrates my meaning.

def startUp():
    print ("grade report\n").center(60).upper()
    print "name".upper(),"course".rjust(22).upper(),"grade".rjust(32).upper()
    print "-" * 60
    processRecords()

def processRecords():
    numOfRecs = 0
    for line in open("grades.txt","r"):
        numOfRecs += 1
        printLine(line)
    return numOfRecs

def printLine(studentRecord):
    name = studentRecord[0:20]
    course = studentRecord[20:50]
    grade = studentRecord[50:51]
    print name, course.rjust(3), grade.rjust(3)

def closeUp(recordCount):
    print "\nTotal courses taken by all students = ",recordCount


startUp()
Josh Smeaton