Search code examples
pythonbacktracking

backtracking not trying all possibilities


so I've got a list of questions as a dictionary, e.g

{"Question1": 3, "Question2": 5 ... }

That means the "Question1" has 3 points, the second one has 5, etc.

I'm trying to create all subset of question that have between a certain number of questions and points.

I've tried something like

questions = {"Q1":1, "Q2":2, "Q3": 1, "Q4" : 3, "Q5" : 1, "Q6" : 2}
u = 3  #
v = 5 # between u and v questions


x = 5   #
y = 10 #between x and y points


solution = []
n = 0

def main(n_):
        global n
        n = n_

        global solution
        solution = []

        finalSolution = []

        for x in questions.keys():
                solution.append("_")

        finalSolution.extend(Backtracking(0))

        return finalSolution


def Backtracking(k):

        finalSolution = []

        for c in questions.keys():
                solution[k] = c
                print ("candidate: ", solution)
                if not reject(k):
                    print ("not rejected: ", solution)
                    if accept(k):
                                finalSolution.append(list(solution))
                    else:
                                finalSolution.extend(Backtracking(k+1))

        return finalSolution


def reject(k):
    if solution[k] in solution: #if the question already exists
        return True
    if k > v: #too many questions
        return True
    points = 0
    for x in solution:
        if x in questions.keys():
            points = points + questions[x]
    if points > y: #too many points
        return True
    return False



def accept(k):

    points = 0
    for x in solution:
        if x in questions.keys():
            points = points + questions[x]
    if points in range (x, y+1) and k in range (u, v+1):
        return True
    return False


print(main(len(questions.keys())))

but it's not trying all possibilities, only putting all the questions on the first index..

I have no idea what I'm doing wrong.


Solution

  • There are three problems with your code.

    The first issue is that the first check in your reject function is always True. You can fix that in a variety of ways (you commented that you're now using solution.count(solution[k]) != 1).

    The second issue is that your accept function uses the variable name x for what it intends to be two different things (a question from solution in the for loop and the global x that is the minimum number of points). That doesn't work, and you'll get a TypeError when trying to pass it to range. A simple fix is to rename the loop variable (I suggest q since it's a key into questions). Checking if a value is in a range is also a bit awkward. It's usually much nicer to use chained comparisons: if x <= points <= y and u <= k <= v

    The third issue is that you're not backtracking at all. The backtracking step needs to reset the global solution list to the same state it had before Backtracking was called. You can do this at the end of the function, just before you return, using solution[k] = "_" (you commented that you've added this line, but I think you put it in the wrong place).

    Anyway, here's a fixed version of your functions:

    def Backtracking(k):
            finalSolution = []
            for c in questions.keys():
                    solution[k] = c
                    print ("candidate: ", solution)
                    if not reject(k):
                        print ("not rejected: ", solution)
                        if accept(k):
                                    finalSolution.append(list(solution))
                        else:
                                    finalSolution.extend(Backtracking(k+1))
            solution[k] = "_"    # backtracking step here!
            return finalSolution
    
    def reject(k):
        if solution.count(solution[k]) != 1: # fix this condition
            return True
        if k > v:
            return True
        points = 0
        for q in solution:
            if q in questions:
                points = points + questions[q]
        if points > y: #too many points
            return True
        return False
    
    def accept(k):
        points = 0
        for q in solution:  # change this loop variable (also done above, for symmetry)
            if q in questions:
                points = points + questions[q]
        if x <= points <= y and u <= k <= v:  # chained comparisons are much nicer than range
            return True
        return False
    

    There are still things that could probably be improved in there. I think having solution be a fixed-size global list with dummy values is especially unpythonic (a dynamically growing list that you pass as an argument would be much more natural). I'd also suggest using sum to add up the points rather than using an explicit loop of your own.