0

I am building a program that will solve sudoku puzzles. Here is the code:

def set_table(boxes_v, boxes_h, tiles_v, tiles_h):
""" boxes_v = boxes on vertical side of the whole table;
    boxes_h = boxes on horixontal side of the whole table;        
    tiles_v = tiles on vertical line in each box;
    tiles_h = tiles on horizontal line in each box.
"""

    total_boxes = boxes_v * boxes_h
    tiles_in_box = tiles_v * tiles_h

    return [[{None : [False, 0]} for x in range(1, tiles_in_box + 1)] for a in range(total_boxes)]



def insert_numbers(table, numbers_and_positions):
""" table = sudoku table set up in "set_table";
    numbers_and_postions = dictionary containing already given numbers and their positions in the table.
"""

    noPos = numbers_and_positions

    for number in noPos:
        box = noPos[number][0]
        tile = noPos[number][1]
        table[box][tile] = {None : [True, number]}

    return table



def test(table, index, number):
    "Tests if number is suitable in a tile on vertical and horizontal lines"

    box_index, tile_index = index

    lines = {
    0 : [(0,1,2), (0,3,6)],
    1 : [(0,1,2), (1,4,7)],
    2 : [(0,1,2), (2,5,8)],

    3 : [(3,4,5), (0,3,6)],
    4 : [(3,4,5), (1,4,7)],
    5 : [(3,4,5), (2,5,8)],

    6 : [(6,7,8), (0,3,6)],
    7 : [(6,7,8), (1,4,7)],
    8 : [(6,7,8), (2,5,8)]
    }

    box_line_h, box_line_v = lines[index[0]]
    tile_line_h, tile_line_v = lines[index[1]]

    ###################################    horizontal line tester
    taken_numbers_h = []

    for box_index in box_line_h:
        index_counter = 0

    for tile in table[box_index]:

        if index_counter in tile_line_h:              
            taken_numbers_h.append(tile[None][1])

        index_counter += 1

    ###################################    vertical line tester    

    taken_numbers_v = []

    for box_index in box_line_v:
        index_counter = 0

    for tile in table[box_index]:

        if index_counter in tile_line_v:              
            taken_numbers_v.append(tile[None][1])

        index_counter += 1

    ###################################    box tester

    taken_numbers_b = []

    for tile in table[box_index]:
        taken_numbers_b.append(tile[None][1])

    ###################################

    taken_numbers = taken_numbers_h + taken_numbers_v + taken_numbers_b

    if number in taken_numbers:
        return True

    elif number not in taken_numbers:
        return False




def reset_key(dictionary, old_key, new_key, value):
"Resets a key of a dictionary to a different name"

    dictionary[new_key] = dictionary[old_key]
    del dictionary[old_key]

    return dictionary




def solve(table):
""" Solves the sudoku puzzle
"""
    box_index = 0
    tile_index = 0
    safe = True

    def repeat():

        tile[None][1] += 1

        if tile[None][1] > 9:
            tile[None][1] = 0
            safe = False
            tile_index -= 1

        elif tile[None][1] <= 9:

            if test(table, [box_index, tile_index], tile[None][1]) is True:
                repeat()

            elif test(table, [box_index, tile_index], tile[None][1]) is False:
                tile_index += 1
                safe = True


    valid = False
    while valid is not True:

        box = table[box_index]
        tile = box[tile_index]

        if tile[None][0] is True:

            if safe is True:
                tile_index += 1
                safe = True

            elif safe is False:
                tile_index -= 1

        elif tile[None][0] is False:
            repeat()

What I do in Python shell is:

>>>table = set_table(3,3,3,3)
>>>table = insert_numbers(table, {0 : [0,4]})
>>>solve(table)

What I expect to happen is the program changing the value of every dictionary on table[0] (box 1) to {None : [False, 1]}, {None : [False, 2]}, {None : [False, 3]} and so on until it reaches 9 at the last small box within the first big box of the whole grid because the code should cause an index error. This is because when it reaches the last small box in the first big box, an negative test result for violating the rules should come up for numbers 1-8 because they are obviously already in the box. I do not see this error, however, but this one instead:

Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
solve(t)
File "C:\Users\cdobr\Desktop\Sudoku Solver.py", line 153, in solve
repeat()
File "C:\Users\cdobr\Desktop\Sudoku Solver.py", line 129, in repeat
if test(table, [box_index, tile_index], tile[None][1]) is True:
UnboundLocalError: local variable 'tile_index' referenced before assignment

What am I supposed to do to fix this? It is as if the function inside the function could not see the parent's variables.

1 Answer 1

1

tile_index is a non-local variable, but you do not specify it to be so. You can do this explicitly using nonlocal:

def repeat():
    nonlocal tile_index

Why is nonlocal to be specified for this and not for any of the other non-local variables? Because of this line:

tile_index -= 1

A recursive call causes this to be executed. However, when doing an assignment, python assumes the variable is local unless otherwise mentioned. None of the other non-local variables are assigned to/updated, so this does not apply.

Sign up to request clarification or add additional context in comments.

2 Comments

But isn't tile_index local for all of the function solve, including the function repeat, since it's inside solve? Besides, why doesn't it do that for box_index?
@ChrisCroissant Hmm. thought I had replied. tile_index is nonlocal to repeat, because it belongs in the enclosing scope of function solve. Also, it does not do that for box_index because box_index isn't assigned to. The statement tile_index += .... is basically an assignment.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.