-2

Some backstory

I made a Sudoku solver which works if any given cell has only 1 option to fill in it's given row/column/group. But this solver doesn't work if the options are above 1, so I tried to make a recursive solver which takes turn for every possible option in each cell, I tried several methods to branch the choices, but I can't find a way to make it go back to a state and take the next option. My program seem to be always taking the same path.

My code work as follows:

  • It start by find every possible moves for every cell

  • Then it sort them by the least amount of options.

  • If there's a length 1 option in a given cell it fills it and restart,

  • If not, it will loop over the options

Ideally it would keep going until exhaustion and go back to the last option chosen and take the other one. But it's not doing that, it is repeating itself.

What I'm trying to do now:

After some research I found that what I'm trying to do is called Depth-First-Search,

I found this implementation of the DFS, however I'm not being able to implement it in my program.

Here's my attempt:

def best_moves():
    # get_possible_moves() returns a list of all the available moves for every cell
    # sorting it and taking the first element will get the one
    # that has the least amount options
    return [move for move in sorted(board.get_possible_moves())[0]]

def get_next_nodes(move):
    board.make_this_move(move)
    return best_moves()

def dfs(graph, start):
    visited, stack = set(), [start]
    while stack:
        vertex = stack.pop()
        if vertex not in visited:
            visited.add(vertex)
            nodes = []
            for move in graph[vertex]: 
                nodes.extend(get_next_nodes(move))
            nodes = [node for node in nodes if node not in visited]
            stack.extend(nodes)
    return visited

b = Board(puzzle_hard)
b.create_board()
graph = {'root':best_moves()}
dfs(graph,'root')

It gives:

Traceback (most recent call last):
  File "C:\Users\user\Documents\python\sudoku\sudoku_solver.py", line 171, in <module>
    dfs(graph,'root')
  File "C:\Users\user\Documents\python\sudoku\sudoku_solver.py", line 162, in dfs
    for move in graph[vertex]:
KeyError: <__main__.PossibleMove instance at 0x0223DB48>

PossibleMove is a class that I made to handle the possible moves for a given cell. I could try to add it to the graph, but in order to do that I need to search it again, and I can't do that because i'm in the middle of the DFS.

tl;dr

How to implement DSF without having all the nodes in the graph beforehand.

Community
  • 1
  • 1
f.rodrigues
  • 3,499
  • 6
  • 26
  • 62

1 Answers1

0

After several days I manage to make it work.

Following some guidelines in this article(mostly by using the puzzle as a string for the graph node)

The key element of this program is the game state, the board prior a move, I was trying to use really complicated methods to store the game state, but all of them required some knowledge that I don't have(yet). In this method now i'm getting the game state as a string: eg:

53..7....6..195....98....6.8...6...34..8.3..17...2...6.6....28....419..5....8..79

is the same as:

[[5, 3, 4, 6, 7, 8, 9, 1, 2],
[6, 7, 2, 1, 9, 5, 3, 4, 8],
[1, 9, 8, 3, 4, 2, 5, 6, 7],
[8, 5, 9, 7, 6, 1, 4, 2, 3],
[4, 2, 6, 8, 5, 3, 7, 9, 1],
[7, 1, 3, 9, 2, 4, 8, 5, 6],
[9, 6, 1, 5, 3, 7, 2, 8, 4],
[2, 8, 7, 4, 1, 9, 6, 3, 5],
[3, 4, 5, 2, 8, 6, 1, 7, 9]]

Every time I meet a path I store the game state, try this path, if it find another path, I add those to the previous state.

So in the future it will add those in the set as new nodes, and find their possible paths.

In simpler example that would be:

'123456..'

Could lead to either:

'123456789'
'123456798'

so the graph representation is:

graph = {'123456..':set(['123456789','123456798'])}

The code is:

def possible_moves(puzzle):
    puzzle = string_to_puzzle((puzzle) # convert the puzzle to a better format(array of 9x9)
    moves = get_next_moves(puzzle) # get the possible moves for the puzzle
    results = set()
    if moves: # if no moves mean, the puzzle is filled or the choices were wrong
        for move in moves:
            this_puzzle = copy(puzzle)
            this_puzzle[move.row][move.column] = move.value
            results.add(puzzle_to_string(this_puzzle)) # convert it back to string and add it to possible moves.
    return results

def dfs(graph, start):
    visited, stack = set(), [start]
    while stack:
        vertex = stack.pop()
        if win(vertex): # check if the current node is the winner
            return string_to_array(vertex) # returns the puzzle compleded in a 9x9 array
        if vertex not in visited:
            visited.add(vertex)
            graph[vertex] = possible_moves(vertex) # finds the next game states for the vertex
            stack.extend(graph[vertex] - visited)
    return "no possible solution"
f.rodrigues
  • 3,499
  • 6
  • 26
  • 62