-1

这是我的第一篇文章。我有一个询问学生人数的功能。然后,对于每个学生,前三行包含以下信息:学生 ID、姓名和该学期修读的课程数量。现在,对于每门课程,都会列出课程编号、学分和学生获得的分数百分比。

def rawdata():
    semester = 1
    if semester == 1:
        raw_file = open('raw_data.txt', 'a')
        total = 0.0
        total2 = 0.0
        num_students = int(input('Enter number of students: '))        
        for student in range(num_students):
            raw_file.write('Start' + '\n')
            student_id = input('Enter student ID: ')
            name = input('Enter Name: ')
            num_classes = int(input('Enter number of courses taken: '))
            raw_file.write(student_id + '\n')
            raw_file.write(name + '\n')
            raw_file.write(str(num_classes) + '\n')
            for classes in range(num_classes):
                course_number = input('Enter Course Number: ')
                credits = int(input('Enter Credit Hours: '))
                GPA1 = float(input('Enter percentage grade for class: '))
                raw_file.write(course_number + '\n')
                raw_file.write(str(credits) + '\n')
                raw_file.write(str(GPA1) + '\n')
                total += credits
            raw_file.write('End' + '\n')
        raw_file.close()
        print('Data has been written')

所有数据都列在一个 txt 文件中,现在我需要从我的 raw_data.txt 中提取这些信息,如下所示(因输入而异):

Start
eh2727
Josh D
2
MAT3000
4
95.0
COM3000
4
90.0
End
Start
ah2718
Mary J
1
ENG3010
4
100.0
End

并对其进行处理,以便我可以计算每个学生的 GPA。我让每个学生的信息块都包含在开始/结束中,我不知道如何在我的处理函数中读取这些信息以便计算他们的 GPA。这是我到目前为止所拥有的:

def process():
    data = open('raw_data.txt', 'r')
    results = open('process_results.txt', 'w')
    buffer = []
    for line in data:
        if line.startswith('Start'):
            buffer = []
        buffer.append(line)
        if line.startswith("End"):
            for outline in buffer:
                results.write(outline)

这只是将其全部写入我的结果文本,我不知道如何单独处理每个信息块来计算 GPA。任何帮助将不胜感激。

4

3 回答 3

1

您需要开发一个状态机来处理学生记录。你的'if line.strip() == 'Start' 走在正确的轨道上,这是一个指示记录开始的哨兵。此时你可以做的是设置一个标志,processStudentRecord = true,所以下一次通过'for line in data'你知道你得到的行是记录的一部分。设置标志后,您应该打破该 if 循环,这样您就不必拥有一堆 elif。

processStudentRecord = False
for line in data:
  if line.strip() == 'Start':
    processStudentRecord = True
    expecting            = "student_id"
    # break here so you go immediately to the next line
  if line.strip() == 'End':
    processStudentRecord = False
    # break here so you go immediately to the next line
  if processStudentRecord:
    # keep track of where you are in the student record
    if expecting == "student_id":
      # extract the student name and then proceed to the next expected line
      expecting = "student_name"
    elif expecting == ""

等等等等。请注意,这是一种“程序化”方法——可以发明面向对象或功能性的解决方案。

于 2012-07-05T21:09:11.940 回答
1

由于将数据写入 .txt 文件是您自己的代码,因此您可以考虑以更容易和/或更容错的格式编写它以供机器读取,例如 JSON 或 XML。或者,您可能需要考虑使用 pickle 或 cpickle 来序列化数据并再次读入。

无论如何,关于你的问题:如何读取文件。不幸的是,您没有告诉我们您想对解析后的数据做什么。我假设你想在这里打印它。通常,您当然会创建一个很好的类或描述学生和课程的类。

为了解析像你这样的文件,我经常使用字符串方法 split() 。split() 是你最好的朋友。有关字符串方法的更多信息,请参阅python 文档

f = open('raw_data.txt', 'rt')
data = f.read()

students = data.split('Start\n')[1:]

for s in students:
    lines = s.split('\n')
    id = lines[0]
    name = lines[1]

    nrcourses = int(lines[2])

    line = 2
    courses = []
    for n in range(nrcourses):
        number = lines[line+1]
        credit = lines[line+2]
        score = lines[line+3]
        courses.append((number, credit, score))
        line += 3

    print 'id: %s; name %s; course list %s' % (id, name, courses)

f.close()
于 2012-07-05T21:09:50.070 回答
0

这是相当多的代码,但如果你一直跟踪它直到你理解它是如何工作的,你会学到很多东西。

首先,我们需要获取一个班级标记并将其转换为分数。你可以把它写成 13 的级联if,但我喜欢数据驱动的方法:

import bisect

def grade_points(pct):
    grade  = [  0,  50,  53,  57,  60,  63,  67,  70,  73,  77,  80,  85,  90]
    points = [0.0, 0.7, 1.0, 1.3, 1.7, 2.0, 2.3, 2.7, 3.0, 3.3, 3.7, 4.0, 4.0]
    if 0 <= pct <= 100:
        # find the highest grade <= pct
        idx = bisect.bisect_right(grade, pct) - 1
        # return the corresponding grade-point
        return points[idx]
    else:
        raise ValueError('pct value should be in 0..100, not {}'.format(pct))

接下来,我们想要一个 Student 类来轻松跟踪每个学生的数据

class Student(object):
    str_format = '{id:>8}  {name}  {gpa}'

    def __init__(self, *args):
        if len(args)==1:    # copy constructor
            student = args[0]
            self.id, self.name, self.classes = student.id, student.name, student.classes
        elif len(args)==3:  # "id", "name", [classes,]
            self.id, self.name, self.classes = args
        else:
            raise ValueError('Failed call to {}.__init__{}'.format(type(self), args))

    @property
    def gpa(self):
        points = sum(hour*grade_points(grade) for code,hour,grade in self.classes)
        hours  = sum(hour                     for code,hour,grade in self.classes)
        return points / hours

    def __str__(self):
        return type(self).str_format.format(id=self.id, name=self.name, classes=self.classes, gpa=self.gpa)

    def __repr__(self):
        return "{}('{}', '{}', {})".format(type(self).__name__, self.id, self.name, self.classes)

所以你可以创建一个学生并像这样找到她的 GPA:

sally = Student('ab2773', 'S Atkins', [
    ('MAT3000', 4, 81.0),
    ('ENG3010', 4, 85.0)
])
print sally     # '  ab2773  S Atkins  3.85'

现在我们需要能够将 Student 流式传输到文件和从文件中传输。从 OOP 的角度来看,这有点痛苦,因为 Student 对象实际上不需要了解 File 对象的任何信息,反之亦然,而且更重要的是因为我们希望将您升级到更好的文件格式 - Student object 绝对不需要知道多个不兼容的文件类型。

我已经通过子类化 Student 来解决这个问题;我以这样一种方式编写了该Student.__init__方法,即我可以来回转换并且不必为子类重写它,因此子类只知道如何将自己转换为您讨厌的文件格式

class NastyFileStudent(Student):
    @classmethod
    def from_strings(cls, strings):
        if len(strings) > 3 and len(strings) == 3 + int(strings[2])*3:
            codes  = strings[3::3]
            hours  = map(int,   strings[4::3])
            grades = map(float, strings[5::3])
            return Student(strings[0], strings[1], zip(codes, hours, grades))
        else:
            # not enough data returned - probably end of file
            return None

    def __str__(self):
        data = [self.id, self.name, str(len(self.classes))] + [str(i) for c in self.classes for i in c]
        return '\n'.join(data)

文件只知道它有学生数据,但对内容一无所知

class NastyFile(object):
    START = 'Start'
    END   = 'End'

    @staticmethod
    def _read_until(endfn, seq):
        is_end = endfn if callable(endfn) else lambda s: s==endfn
        data = []
        for s in seq:
            if is_end(s):
                break
            else:
                data.append(s)
        return data

    def __init__(self, name, mode='r'):
        self.name = name
        self.mode = mode
        self._f = open(name, mode)
        self.lines = (ln.strip() for ln in self._f)

    def __del__(self):
        self._f.close()

    def __iter__(self):
        return self

    def next(self):
        _       = NastyFile._read_until(NastyFile.START, self.lines)
        strings = NastyFile._read_until(NastyFile.END,   self.lines)

        student = NastyFileStudent.from_strings(strings)
        if student is None:
            raise StopIteration()
        else:
            return student

    def read(self):
        return list(self)

    def write(self, s):
        if not hasattr(s, '__iter__'):
            s = [s]
        for student in s:
            self._f.write('{}\n{}\n{}\n'.format(NastyFile.START, str(NastyFileStudent(student)), NastyFile.END))

现在我们可以像这样读写学生记录文件了

>>> students = NastyFile('student_records.txt').read()

>>> for s in students:
...     print s
  eh2727  Josh D  4.0
  ah2718  Mary J  4.0

>>> students.append(sally)

>>> students.sort(key=lambda s: s.name.rsplit(None,1)[-1])  # sort by last name

>>> for s in students:
...     print s
  ab2773  S Atkins  3.85
  eh2727  Josh D  4.0
  ah2718  Mary J  4.0

>>> newfile = NastyFile('new_records.txt', 'w')
>>> newfile.write(students)

>>> for i,s in enumerate(NastyFile('new_records.txt'), 1):
...     print '{:>2}: {}'.format(i, s)
 1:   ab2773  S Atkins  3.85
 2:   eh2727  Josh D  4.0
 3:   ah2718  Mary J  4.0
于 2012-07-06T02:43:22.270 回答