Backtracking 8 Queens Python problems

3.4k views Asked by At

I've started solving the 8 queens problem with backtracking in Python. Everything is nice & fine. It even printed out the first answer. However, it stuck itself on its first backtracking try.

The task sounded in that way:

Implement a Python function that solves the 8 queens puzzle. The 8 queen puzzle consists of placing 8 queens on a chess board, so that, none of the queens could capture any other. Note that queens can move orthogonally or diagonally in any direction.

You should implement a function solve() that when called, it prints the first solution of the puzzle and then it awaits for input. Once the user presses ‘enter’, the next solution is printed, and so on.

- Your program should be able to find all the solutions for the puzzle and each solution only once. '

- It should be easy to modify your program, so that, it works for different board sizes. Hints:

- In any row, there is exactly one queen. Hence, all you need to compute is the column in which each of the 8 queens can be placed.

- You should implement a recursive function solve(n) that finds a place for nth+1 the queen and then calls itself recursively for the n+1 queen (unless all the queens have been placed). It should systematically explore all the possibilities using backtracking.

- You are allowed (and encouraged) to define extra functions (other than solve() ) to improve the quality of your code if necessary.


    import numpy as np
    grid = np.zeros((8, 8), dtype = int)
    
    
    def possible(y, n):
        global solved
        global grid
        for i in range(0, 8):
            if grid[y][i] == n:
                return False
        try:
            for item in solved[str(y)]:
                if grid[y].all() == item.all():
                    return False
        except KeyError:
            return True
        return True
    
    max_y = 7
    max_x = 7
    
    def print_grid():
        global grid
        for line in grid:
            for square in line:
                if square == 0:
                    print(".", end = " ")
                else :
                    print("Q", end = " ")
            print()
    
    solved = {}
    
    def prefilled_solved():
        global solved
        for i in range(0, len(grid[0])):
            solved[f"{str(i)}"] = []
    
    def solve(y=0):
        global grid
        global solved
        while y < 8:
            for x in range(0, 8):
                if grid[y][x] == 0:
                    if possible(x, 1):
                        grid[y][x] = 1
                        solved[f"{str(y)}"].append(grid[y])
                        y += 1
                        solve(y)
                        #y -= 1 or y = 0 or y -=2
                        # backtracking - bad choice
                        # grid[y][x] = 0
    
        print_grid()
        print(grid)
        return
        input("More?")
    
    if __name__ == '__main__':
        prefilled_solved()
        solve()

I've followed the @mkam advice, Now I've got the random constellation of Queen but I've got rid of recursion altogether.

```import numpy as np
grid = np.zeros((8, 8), dtype = int)
from random import randint, shuffle, choice
from itertools import permutations


constellations_drawn  = []


def print_grid():
    global grid
    for line in grid:
        for square in line:
            if square == 0:
                print(".", end = " ")
            else :
                print("Q", end = " ")
        print()

solved = []

def prefilled_solved():
    global solved
    new_board = ['1', '2', '3', '4', '5', '6', '7', '8']
    new_board_i = ''.join(new_board)
    solved = permutations(new_board_i, 8)



def solve(y=0):
    global grid
    global solved
    global constellations_drawn
    list_solved = list(solved)
    len_solved = len(list_solved)
    board_drawn = list_solved[randint(0, len_solved-1)]
    board_drawn_str = ''.join(board_drawn)
    while board_drawn_str in constellations_drawn:
        board_drawn = list_solved[randint(0, len_solved - 1)]
    new_board_list = [int(item) for item in board_drawn]
    for i, x in enumerate(new_board_list):
        if grid[i-1][x-1] == 0:
            grid[i-1][x-1] = 1
            #y += 1
            #solve(y)
            #y -= 1 or y = 0 or y -=2
            # backtracking - bad choice
            # grid[y][x] = 0
    constellations_drawn.append(board_drawn_str)
    print_grid()
    print(grid)
    return
    input("More?")

if __name__ == '__main__':
    prefilled_solved()
    solve()

I've merged the code of @mkam and mine. And it works. I still use numpy ndarray.

import numpy as np

    from numpy.core._multiarray_umath import ndarray
    
    
    def print_grid(solutions_found, board) -> None:
        line: ndarray
        len_board = len(board)
        grid: ndarray = np.zeros((len_board, len_board), dtype=int)
        for i, number in enumerate(board):
            grid[i - 1][number - 1] = 1
        for line in grid:
            for square in line:
                if square == 0:
                    print(".", end=" ")
                else:
                    print("Q", end=" ")
            print()
        print(f'Solution - {solutions_found}')
    
    
    def solve(boardsize, board=[], solutions_found=0):
        if len(board) == boardsize:
            solutions_found += 1
            print_grid(solutions_found, board)
        else:
            for q in [col for col in range(1, boardsize + 1) if col not in board]:
                if is_safe(q, board):
                    solutions_found = solve(boardsize, board + [q], solutions_found)
        return solutions_found
    
    
    def is_safe(q, board, x=1):
        if not board:
            return True
        if board[-1] in [q + x, q - x]:
            return False
        return is_safe(q, board[:-1], x + 1)
    
    
    if __name__ == '__main__':
        solve(8)
1

There are 1 answers

2
Mario Camilleri On BEST ANSWER

This is an example of how the 8-Queens problem can be solved recursively, using a simple list to represent the board. A list such as [8, 4, 1, 3, 6, 2, 7, 5] represents the 8 rows of a chessboard from top to bottom, with a Q in the 8th column of the top row, the 4th column of the 7th row, the 1st column of the 6th row ... and the 5th column of the bottom row.

A solution is built starting with an empty board [] by placing a Q in the next row in a column position where it cannot be taken. Possible positions are columns which have not already been taken earlier (this is the for loop in function solve). For each of these possible column positions, function issafe checks whether the position is safe from being taken diagonally by the Qs already on the board. If the position is safe, the solution board is extended by another row and the solution recurses until the board is filled (len(board) == boardsize), at which point the solution count is incremented and the board is displayed.

Note that the function solve works for any size of square chessboard - the desired size is passed as a parameter to solve, and the function returns the total number of solutions found.

Hope this helps explain how the 8-Queens problem can be solved recursively WITHOUT numpy.

def display(solution_number, board):
    row = '|   ' * len(board) + '|'
    hr  = '+---' * len(board) + '+'
    for col in board:
        print(hr)
        print(row[:col*4-3],'Q',row[col*4:])
    print(f'{hr}\n{board}\nSolution - {solution_number}\n')

def issafe(q, board, x=1):
    if not board: return True
    if board[-1] in [q+x,q-x]: return False
    return issafe(q, board[:-1], x+1)

def solve(boardsize, board=[], solutions_found=0):
    if len(board) == boardsize:
        solutions_found += 1
        display(solutions_found, board)
    else:
        for q in [col for col in range(1,boardsize+1) if col not in board]:
            if issafe(q,board):
                solutions_found = solve(boardsize, board + [q], solutions_found)
    return solutions_found

if __name__ == '__main__':
    solutions = solve(8)
    print(f'{solutions} solutions found')

You mention using yield - this is also possible, and will transform solve into a generator, producing one solution at a time. Your program can then use a for loop to receive each solution in turn and process it as required. The following yield solution works with Python v.3.3 onwards because it uses yield from:

def display(solution_number, board):
    row = '|   ' * len(board) + '|'
    hr  = '+---' * len(board) + '+'
    for col in board:
        print(hr)
        print(row[:col*4-3],'Q',row[col*4:])
    print(f'{hr}\n{board}\nSolution - {solution_number}\n')

def issafe(q, board, x=1):
    if not board: return True
    if board[-1] in [q+x,q-x]: return False
    return issafe(q, board[:-1], x+1)

def solve(boardsize, board=[]):
    if len(board) == boardsize:
        yield board
    else:
        for q in [col for col in range(1,boardsize+1) if col not in board]:
            if issafe(q,board):
                yield from solve(boardsize, board + [q])

if __name__ == '__main__':
    for solutionnumber, solution in enumerate(solve(8)):
        display(solutionnumber+1, solution)

If the recursive function issafe appears confusing, here is a non-recursive version:

def issafe(q, board):
    x = len(board)
    for col in board:
        if col in [q+x,q-x]: return False
        x -= 1
    return True