If in statement problems

Viewed 77

I am attempting to make a plan, which is a list of classes that can only be added when the required classes have been completed or the co-requisite classes are being taken in the same semester.

Below I have my code that almost works but it always reuses the classes even though they have already been completed/used. I tried to prevent this with and (class_list[i][0] not in classes_done), I was hoping that it wouldn't go into the if statement but it seems like it's being ignored.

The rest of this if statement seems to work fine. (class_list[i][3] == '' or class_list[i][3] in classes_done) does this class have a required completed class if yes has it been completed? (class_list[i][2] in classes_for_semester or class_list[i][2] == '')does this class have a co-requisite class if yes is it in the class_for_semester or already completed?

The class_list variable is organized like this['name', 'credit', 'co-requisite', 'required completed classes', 'empty']. I added the other variables as comments to show what they look like.

class PlanGenerator:
def generator(max_credit_allowed, min_credit_allowed, classes_done, class_list):
    classes_for_semester = []
    credits_for_semester = 0
    semester = 0
    full_plan = []
    # class_list = [['MA 241 ', '4', '', '', ''], ['PS 150 ', '3', 'MA 241 ', '', ''], ['UNIV 101', '1', '', '', ''], ['COM 122', '3', '', '', ''], ...]
    # max_credit_allowed = 16
    # min_credit_allowed = 12
    # classes_done=['UNIV 101']
    while len(classes_done) != len(class_list): # keep going until all classes are used
        while int(min_credit_allowed) > credits_for_semester: # keep going until at least the minimum credits are in the semester
            semester += 1
            for i in range(len(class_list)): # looping over the class list
                if int(class_list[i][1]) + credits_for_semester < max_credit_allowed: #if this class was to be added would it go over the max credit for semester if yes go to next class
                    if (class_list[i][3] == '' or class_list[i][3] in classes_done) and (class_list[i][2] in classes_for_semester or class_list[i][2] in classes_done or class_list[i][2] == '') and (class_list[i][0] not in classes_done):
                        classes_for_semester.append(class_list[i][0])
                        credits_for_semester += int(class_list[i][1])
                        print('classes for semester', classes_for_semester)
                        print('semester credits', credits_for_semester)
            classes_done.append(classes_for_semester)
            full_plan.append(semester)
            full_plan.append(classes_for_semester)
            print('full plan', full_plan)
            classes_for_semester = []
            credits_for_semester = 0
    print('done')
    print(full_plan)

I hope my explanation makes sense. Maybe somebody can understand my mistake and help me find a good solution. Also if you have anything that you see would make this code more simple please let me know.

Much appreciated

1 Answers

First, your while int(min_credit_allowed) > credits_for_semester line is leading to an infinite loop. It needs to be changed to

while len(classes_done) != len(class_list) and int(min_credit_allowed) > credits_for_semester: # Remove the second while loop

Secondly, you're appending a list to a list, so you get a 2-D list for classes_done with classes_done.append(classes_for_semester)

This should be

classes_done += classes_for_semester

so that you add the items from classes_for_semester into classes_done, rather than adding a list.

Your new code should look like this:

def generator(max_credit_allowed, min_credit_allowed, classes_done, class_list):
    classes_for_semester = []
    credits_for_semester = 0
    semester = 0
    full_plan = []
    # class_list = [['MA 241 ', '4', '', '', ''], ['PS 150 ', '3', 'MA 241 ', '', ''], ['UNIV 101', '1', '', '', ''], ['COM 122', '3', '', '', ''], ...]
    # max_credit_allowed = 16
    # min_credit_allowed = 12
    # classes_done=['UNIV 101']
    while len(classes_done) != len(class_list) and int(min_credit_allowed) > credits_for_semester: # keep going until at least the minimum credits are in the semester
        semester += 1
        for i in range(len(class_list)): # looping over the class list
            if int(class_list[i][1]) + credits_for_semester < max_credit_allowed: #if this class was to be added would it go over the max credit for semester if yes go to next class
                if (class_list[i][3] == '' or class_list[i][3] in classes_done) and (class_list[i][2] in classes_for_semester or class_list[i][2] in classes_done or class_list[i][2] == '') and (class_list[i][0] not in classes_done):
                    classes_for_semester.append(class_list[i][0])
                    credits_for_semester += int(class_list[i][1])
                    print('classes for semester', classes_for_semester)
                    print('semester credits', credits_for_semester)
        classes_done += classes_for_semester
        full_plan.append(semester)
        full_plan.append(classes_for_semester)
        print('full plan', full_plan)
        classes_for_semester = []
        credits_for_semester = 0
    print('done')
    print(full_plan)

I would highly recommend using None instead of '' for the non-existent values, that way you can do a simple value is None check instead of an equality check to an empty string.

For the lists of class information you're passing in, I would change them to classes, dictionaries, or namedtuples (find out more about them here) so that you can easily refer to the values by name rather than numbers. class_list[i].class_name or class_list[i]['class_name'] are a lot easier to debug in the future than magic indices. You can even change your for loop to use the actual class details as a variable instead of i in range(len(class_list)) like so:

for c in class_list:
   if int(c.credits) .... # Using a class or namedtuple approach as suggested above

And one minor thing that probably isn't a huge issue but could become a concern if these lists were to grow long: consider using sets instead of lists for storing things like classes_done and classes_for_semester. It also prevents duplicates from being stored (assuming you don't want to store the same class more than once).


To provide a concrete example of the namedtuple suggestion, you can do the following:

from collections import namedtuple
ClassList = namedtuple('ClassList', ['class_name', 'credits', 'coreq', 'prereq'])

class_list = [
    ClassList(class_name='MA 241', credits=4, coreq=None, prereq=None),
    ClassList(class_name='PS 150', credits=3, coreq='MA 241', prereq=None),
    # ...
]

So your for loop becomes

for c in class_list:
    if c.credits + credits_for_semester < max_credits_allowed:
        if (c.prereq is None or c.prereq in classes_done) and \
           (c.coreq in classes_for_semester or c.coreq in classes_done or c.coreq is None) and \
           (c.class_name not in classes_done):
            classes_for_semester.append(c.class_name)
            credits_for_semester += c.credits
classes_done += classes_for_semester
full_plan.append(semester)
full_plan.append(classes_for_semester)
classes_for_semester = []
credits_for_semester = 0
Related