0

I have a spread sheet containing student names and test scores in this format:

first name, last, score

Each student can take the test up to three times, however they are given another row if they attempt the test more than once, example:

John, Smith, 80
Sally, Williams, 90
John, Smith, 100

I am trying to create Student objects for each student and add these to a ClassOfStudents object. But I cannot figure out how to avoid creating 'John Smith' twice. Here are the two classes:

class Student:
    def __init__(self, first_name, last_name ):
        self.first_name = first_name
        self.last_name = last_name
        self.score_first_attempt = 0
        self.score_second_attempt = 0
        self.score_third_attempt = 0

class ClassOfStudents:
    """Represents one class"""
    def __init__(self, cohort, assignment_name):
        """ intitalize empty list, will hold Student objects """
        self.students = []

    def add_student(self, student_obj):
        self.students.append(student_obj)

and here is my main.py where I read the excel data and create objects from said data:

from student import Student, ClassOfStudents
from openpyxl import load_workbook

# intentionally left out openpxyl code but am reading excel data via the 'sheet_obj' variable

# initialize object that will hold all Student objects
class_of_students= ClassOfStudents() 

# Create Student objects and add them to class_of_students
for i in range(1, 3):
    first_name = sheet_obj.cell(row = i, column = 2).value
    last_name = sheet_obj.cell(row =i, column = 1).value
    score = sheet_obj.cell(row = i, column= 3).value
    

    student_obj = Student(first_name, last_name) # create student object

    # if there are no Student objects in class_of_students object, add the first one
    if not class_of_students_obj.students:
        class_of_students.add_student(student_obj)

    # loop through class_of_students, if student is already included in class_of_students do not add this iterations student_obj, just discard it
    for student in class_of_students_obj.students:
        if student.first_name == first_name and student.last_name == last_name:
            # logic for retrieving existing object and adding score_second_attempt value would go here
        else:
            class_of_students_obj.add_student(student_obj)

My code creates 3 Student objects and adds them all to class_of_students (John Smith is created twice). I believe this is because 'Sally Williams' is eventually being compared to 'John Smith', thus creating the third object. I think my attempt is approaching this in the completely wrong way. Can anyone offer a better approach to avoid creating duplicate Student objects that represent the same physical student? Thanks for any help. (I also left out adding the score_first_attempt value intentionally since I need to avoid duplicates before focusing on that)

Justin
  • 339
  • 1
  • 3
  • 15
  • 1
    I would change `ClassOfStudents.add_student` method to check if the class already has this student. This nicely encapsulates the idea that you don't have same student twice in the same class (although, you could definitely have 2 people with the same name). If you had two classes of students, it would be OK to have same person in both, so it makes sense to let ClassOfStudents do the check. – Marian Mar 03 '21 at 19:19
  • 1
    Two thoughts here: 1) you might want to store student attempts as a list instead of hard coding them: what if you want to add more later? If you want it to be strictly 3 attempts, you can enforce this by raising an exception if someone tries to add a fourth attempt or simply passing instead of adding. 2) You might want to look into [overriding contains](https://stackoverflow.com/questions/2217001/override-pythons-in-operator) so you can cleanly write `if student in class`. – Kraigolas Mar 03 '21 at 19:19

2 Answers2

1

Just add a loop variable:

exists = False    
for student in class_of_students_obj.students:
    if student.first_name == first_name and student.last_name == last_name:
        exists = student
        break
if exists:
    # logic for retrieving existing object and adding score_second_attempt value would go here
    # the student object of the particular name is in exists object
else:
        class_of_students_obj.add_student(student_obj)

In main.py:

from student import Student, ClassOfStudents
from openpyxl import load_workbook

# intentionally left out openpxyl code but am reading excel data via the 'sheet_obj' variable

# initialize object that will hold all Student objects
class_of_students= ClassOfStudents() 

# Create Student objects and add them to class_of_students
for i in range(1, 3):
    first_name = sheet_obj.cell(row = i, column = 2).value
    last_name = sheet_obj.cell(row =i, column = 1).value
    score = sheet_obj.cell(row = i, column= 3).value
    

    student_obj = Student(first_name, last_name) # create student object

    # if there are no Student objects in class_of_students object, add the first one
    if not class_of_students_obj.students:
        class_of_students.add_student(student_obj)

    # loop through class_of_students, if student is already included in class_of_students do not add this iterations student_obj, just discard it

    exists = False    
    for student in class_of_students_obj.students:
        if student.first_name == first_name and student.last_name == last_name:
            exists = student
            break
    if exists:
        # logic for retrieving existing object and adding score_second_attempt value would go here
        # the student object of the particular name is in exists object
    else:
        class_of_students_obj.add_student(student_obj)
Rishabh Kumar
  • 2,342
  • 3
  • 13
  • 23
1

You can make your class student a dataclass

import dataclasses

@dataclasses.dataclass
class Student:
    first_name: str
    last_name: str
    score_first_attempt: float = 0
    score_second_attempt: float = 0
    score_third_attempt: float = 0

Now you do not have to loop through your class_of_students_obj.students, just make a check if your dataclass is in your list

# Create Student objects and add them to class_of_students
for i in range(1, 3):
    first_name = sheet_obj.cell(row = i, column = 2).value
    last_name = sheet_obj.cell(row =i, column = 1).value
    score = sheet_obj.cell(row = i, column= 3).value

    student_obj = Student(first_name, last_name) # create student object

    # if student is already included in class_of_students do not add this iterations student_obj, just discard it
    if student_obj in class_of_students_obj.students:
        class_of_students_obj.add_student(student_obj)

Than we remove this part in your loop, since its useless now

        # if there are no Student objects in class_of_students object, add the first one
        if not class_of_students_obj.students:
            class_of_students.add_student(student_obj)

If you wan't to make better, you can make the if not class_of_students_obj.students check part inside the add_student method