2

I am solving the N queen problem with iteration (no recursion). Problem I am facing right now is duplicate solutions. For example 4 x 4 board has 2 solutions I am printing 4 solutions, so to say I am finding same solution twice.

Let me get into the code for a better overview:

def solution(self):
        queen_on_board = 0
        for row in range(self.N):
            for col in range(self.N):
                self.board[row][col] = 'Q'
                queen_on_board = queen_on_board + 1
                print ("(row,col) : ", row, col)
                squares_list = self.get_posible_safe_squares(row,col)
                for square in squares_list:
                    for x,y in square.items():
                        if self.isTheQueenSafe(x,y):
                            self.board[x][y] = 'Q'
                            queen_on_board = queen_on_board + 1
                print ("Queen on board", queen_on_board)
                if queen_on_board == 4:
                    self.print_the_board()
                self.reset_the_board()
                queen_on_board = 0

so as you can see I am going over every rows and cols. This particular implementation gives me 4 solution 2 being identical.

(row,col) :  0 1
Queen on board 4
['.', 'Q', '.', '.'] 

['.', '.', '.', 'Q'] 

['Q', '.', '.', '.'] 

['.', '.', 'Q', '.'] 

(row,col) :  0 2
Queen on board 4
['.', '.', 'Q', '.'] 

['Q', '.', '.', '.'] 

['.', '.', '.', 'Q'] 

['.', 'Q', '.', '.']

(row,col) :  1 0
Queen on board 4
['.', '.', 'Q', '.'] 

['Q', '.', '.', '.'] 

['.', '.', '.', 'Q'] 

['.', 'Q', '.', '.'] 

(row,col) :  2 0
Queen on board 4
['.', 'Q', '.', '.'] 

['.', '.', '.', 'Q'] 

['Q', '.', '.', '.'] 

['.', '.', 'Q', '.']

I want to avoid getting duplicates. Would be great if someone can point me to the right direction.

The get_posible_safe_squares() method looks for possible squares in the board where the queen might be safe.

def get_posible_safe_squares(self, row, col):
        ret = []
        for i in range(self.N):
            for j in range(self.N):
                if i != row and j !=col:
                    if i + j != row + col and i - j != row - col:
                        d = { i:j }
                        ret.append(d)
        return ret 
Shadid
  • 4,133
  • 9
  • 30
  • 53
  • Most search algorithms maintain a set of states already explored to avoid this problem. You should do the same. – Gene Feb 18 '17 at 17:29
  • What is `squares_list`? Its name suggest that it is a list of squares, but its elements have an `item` method that itself returns a list of squares, so can you add information about that structure? Your code could do with some comments... – trincot Feb 18 '17 at 17:45
  • In fact all 4 of these boards are just rotations of one solution. It's hard to tell what actually went wrong, but since you place queens arbitrarily, I guess `get_posible_safe_squares` will return arbitrary positions. E.g. place a queen at `(x, y)`, safe squares contains `(a, b), (c, d), (e, f)`. Place a queen at `(a, b)`, safe squares will be `(x, y), (c, d), (e, f)`. –  Feb 18 '17 at 18:19
  • @trincot I have added the get_posible_safe_squares() method. As paul pointed out it returns the arbitrary positions – Shadid Feb 18 '17 at 18:26
  • Right, so `for x,y in square.items():` will actually iterate exactly once. Why do you use a dict for a square, it could be just a tuple...? – trincot Feb 18 '17 at 18:30

2 Answers2

7

The reason you get duplicates is that you also place queens "before" the position where you place the first queen. So your first queen will get its position on each square, but other queens can take their position on a square where in an earlier iteration the first queen had already been put. This means two queens were "swapped", but essentially build towards the same solution.

I tried to rewrite your solution, but then decided to also change the following aspects:

  • It is a pity that the code for placing the first queen is not the same as for placing the other queens. It should be possible to reuse the same code for that.
  • I would not use a singleton dictionary for representing a square. A tuple (i, j) seems more natural than { i:j }.
  • Storing the whole board is maybe overkill when the only information you need is the board size and -- when queens are placed -- the position of the queens. As there cannot be two queens on the same row, you could make this a list of column indexes. So queens[2] == 3 means that there is a queen on row 2 and column 3. Once you have this list, you don't need queens_on_board either, as len(queens) will return that value. The print_the_board can easily produce the dots and "Q"s based on that information.
  • As you have the function isTheQueenSafe, you don't really need get_posible_safe_squares. It was already quite arbitrary that you called it after placing the first queen, but not after placing any other queen.
  • You mixed two naming conventions: camelCase and underscored, so I would go for the latter and use is_queen_safe.

See it run on repl.it

Here is the code:

class Board:
    def __init__(self, size):
        self.N = size
        self.queens = [] # list of columns, where the index represents the row

    def is_queen_safe(self, row, col):
        for r, c in enumerate(self.queens):
            if r == row or c == col or abs(row - r) == abs(col - c):
                return False
        return True

    def print_the_board(self):
        print ("solution:")
        for row in range(self.N):
            line = ['.'] * self.N
            if row < len(self.queens):
                line[self.queens[row]] = 'Q'
            print(''.join(line))

    def solution(self):
        self.queens = []
        col = row = 0
        while True:
            while col < self.N and not self.is_queen_safe(row, col):
                col += 1
            if col < self.N:
                self.queens.append(col)
                if row + 1 >= self.N:
                    self.print_the_board()
                    self.queens.pop()
                    col = self.N
                else:
                    row += 1
                    col = 0
            if col >= self.N:
                # not possible to place a queen in this row anymore
                if row == 0:
                    return # all combinations were tried
                col = self.queens.pop() + 1
                row -= 1

q = Board(5)
q.solution()
trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thanks a lot. This cleared out a lot of things I was unsure and confused about. Appreciate it cheers – Shadid Feb 18 '17 at 19:58
1

Your algorithm misses several cases and the ways to correct the duplicates is quite complex. Instead I'd suggest you emulate recursion.

Start off with a simple recursive function:

def is_safe(board, x, y, c):
    for p in [board[i] for i in range(0, c)]:
        if p[0] == x or p[1] == y or x + y == p[0] + p[1] or x - y == p[0] - p[1]:
            return False

    return True


def nqueen_rec(board, n, c):
    if c == n:
        print(board)
    else:
        for x in range(0, n):
            if is_safe(board, x, c, c):
                board[c] = (x, c)
                nqueen_rec(board, n, c + 1)

The only parameter that changes while stepping deeper into the recursion is c, so we can easily alter the behavior to that of recursion:

def nqueen_nrec(n):
    c = 0
    step = [0 for x in range(0, n + 1)]
    board = [(x, x) for x in range(0, n)]

    while c != -1:
        if c == n:
            print(board)
            c -= 1
            step[c] += 1
        elif step[c] == n:
            c -= 1
            step[c] += 1
        elif is_safe(board, step[c], c, c):
            board[c] = (step[c], c)
            c += 1
            step[c] = 0
        else:
            step[c] += 1

This algorithm keeps track of the current partial solution and the next possible solutions and simulates recursion-depth by starting a new run of the while-loop instead of recursive function-calls.