As title mentioned. Obviously, the code stoped at the second ROW of the output board.I have been checking it for more than a hundred times but I still could not find where it goes wrong. I would be so grateful if you guys can tell me where I did wrong. Below is my code.
def solve(board):
if not find_zero(board):
return True
else:
row, col = find_zero(board)
for value in range(1, 10):
if is_valid(board, row, col, value):
board[row][col] = value
if solve(board):
return True
board[row][col] == 0
return False
def find_zero(board):
for i in range(9):
for j in range(9):
if board[i][j] == 0:
return i, j
return None, None
def is_valid(board, row, col, value):
# Check the row
if value in board[row]:
return False
# Check the column
col_vals = [board[i][col] for i in range(9)]
if value in col_vals:
return False
# Check the grid
x = (row // 3) * 3
y = (col // 3) * 3
for i in range(x, x + 3):
for j in range(y, y + 3):
if board[i][j] == value:
return False
return True
Here is the board and the output of my code.
board = [
[5, 3, 0, 0, 7, 0, 0, 0, 0],
[6, 0, 0, 1, 9, 5, 0, 0, 0],
[0, 9, 8, 0, 0, 0, 0, 6, 0],
[8, 0, 0, 0, 6, 0, 0, 0, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 0, 0, 0, 2, 0, 0, 0, 6],
[0, 6, 0, 0, 0, 0, 2, 8, 0],
[0, 0, 0, 4, 1, 9, 0, 0, 5],
[0, 0, 0, 0, 8, 0, 0, 7, 9],
]
False
[5, 3, 1, 2, 7, 6, 8, 9, 4],
[6, 2, 4, 1, 9, 5, 7, 3, 0],
[0, 9, 8, 0, 0, 0, 0, 6, 0],
[8, 0, 0, 0, 6, 0, 0, 0, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 0, 0, 0, 2, 0, 0, 0, 6],
[0, 6, 0, 0, 0, 0, 2, 8, 0],
[0, 0, 0, 4, 1, 9, 0, 0, 5],
[0, 0, 0, 0, 8, 0, 0, 7, 9]
In addition to the problem Conor pointed out. There also is the problem that board is mutable and you change the board in potentially invalid ways that need to be rolled back. If you make a deepcopy of it every time you pass it to solve it works. Since I only changed the solve method I will post just that:
import copy
def solve(board):
if find_zero(board)[0] is None:
for row in board:
print(row)
return True
else:
row, col = find_zero(board)
for value in range(1, 10):
if is_valid(board, row, col, value):
new_board = copy.deepcopy(board)
new_board[row][col] = value
if solve(new_board):
return True
return False
In the end you get
[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]
True
The find_zero method returns a tuple of length 2. A non-zero length tuple will always evaluate to True, irrespective of its contents, so the conditional at the start of the solve method can never return True.
Thus your code will never recognise a valid solution. You should return None or False from find_zero if it doesn't find any.
Related
The example below is working however, if more zeros (empty cells) are added to the sudoku grid g, it takes longer to run, if it ever finishes. Not asking for a code review here, just I might've overlooked something, and I'd appreciate pointing it out.
def is_solved(grid):
for row, col in zip(grid, [*zip(*grid)]):
for i in range(1, 10):
if (i not in row) or (i not in col):
return False
return True
def square_available(rows, x, y, n):
if 0 <= x < 3:
rows = rows[:3]
elif 3 <= x < 6:
rows = rows[3:6]
else:
rows = rows[6:]
if 0 <= y < 3:
return not any([n in r[:3] for r in rows])
elif 3 <= y < 6:
return not any([n in r[3:6] for r in rows])
else:
return not any([n in r[6:] for r in rows])
def is_valid(grid, x, y, n):
columns = [*zip(*grid)]
return (
square_available(grid, x, y, n) and n not in grid[x] and (n not in columns[y])
)
def solve(grid, empty_cells):
if is_solved(grid):
return grid
for x, y in empty_cells:
for n in range(1, 10):
if is_valid(grid, x, y, n):
grid[x][y] = n
empty_cells.remove((x, y))
if solve(grid, empty_cells):
return grid
else:
grid[x][y] = 0
empty_cells.append((x, y))
if __name__ == '__main__':
solution = [
[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],
]
g = [
[0, 0, 0, 6, 0, 8, 9, 1, 0],
[6, 0, 2, 0, 9, 0, 3, 4, 0],
[1, 9, 8, 3, 0, 0, 0, 6, 7],
[0, 5, 9, 0, 0, 0, 4, 2, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 1, 3, 0, 2, 0, 8, 0, 0],
[9, 6, 0, 5, 3, 7, 2, 8, 0],
[2, 0, 0, 4, 1, 0, 0, 3, 0],
[3, 4, 0, 2, 8, 0, 1, 7, 9],
]
empty = []
for i in range(9):
for j in range(9):
if not g[i][j]:
empty.append((i, j))
solved = solve(g, empty)
assert g == solution
I tried reimplementing the whole thing as shown below, and the results got worse. It's not even capable of solving what's already possible with the implementation above.
from collections import defaultdict
def get_possibilities(rows, columns, x, y, visited):
if (x, y) in visited:
return visited[x, y]
x0 = (x // 3) * 3
x1 = x0 + 3
y0 = (y // 3) * 3
y1 = y0 + 3
possibilities = set()
for n in range(1, 10):
square_rows = rows[x0:x1]
for row in square_rows:
if n in row[y0:y1]:
continue
if (n not in rows[x]) and (n not in columns[y]):
visited[x, y].add(n)
possibilities.add(n)
return possibilities
def solve(rows, columns, empty_cells, visited):
if not empty_cells:
return rows
for x, y in empty_cells:
for n in get_possibilities(rows, columns, x, y, visited):
rows[x][y] = n
columns[y][x] = n
visited[x, y].remove(n)
if solve(rows, columns, empty_cells - {(x, y)}, visited):
return rows
else:
rows[x][y] = 0
columns[y][x] = 0
visited[x, y].add(n)
if __name__ == '__main__':
solution = [
[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],
]
r = [
[0, 0, 0, 6, 0, 8, 9, 1, 0],
[6, 0, 2, 0, 9, 0, 3, 4, 0],
[1, 9, 8, 3, 0, 0, 0, 6, 7],
[0, 5, 9, 0, 0, 0, 4, 2, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 1, 3, 0, 2, 0, 8, 0, 0],
[9, 6, 0, 5, 3, 7, 2, 8, 0],
[2, 0, 0, 4, 1, 0, 0, 3, 0],
[3, 4, 0, 2, 8, 0, 1, 7, 9],
]
c = [list(r) for r in [*zip(*r)]]
cells = set()
for i in range(9):
for j in range(9):
if not r[i][j]:
cells.add((i, j))
v = defaultdict(set)
solved = solve(r, c, cells, v)
assert r == solution
Looking only at the first version of your code, there are a few things you can improve in the solve function:
(A problem also in the second version:) You shouldn't loop over all empty_cells. Instead consider that you must find a solution for the first empty cell, and if after doing all of the recursive effort there is no good value for this first empty cell, then it makes no sense to continue with other empty cells. The first one represents a problem that is unsolvable, so any effort on any other empty cell is useless. So remove that for loop, and just work with one empty cell per recursion level.
You don't need to call is_solved to know that the grid has been completed. You did this better in the second version. Just test not empty_cells or something similar.
Don't call empty_cells.remove and append in each iteration of the inner loop as it is always the same that is removed and added again. Moreover, remove is overkill as you don't really want to search for the entry -- you know at which index it sits. Just do the removal at the start of the process, and add it again at the very end.
With the above changes the performance will greatly improve. Still some little improvements are possible:
Leave the empty_cells list unmutated, and instead pass along an index in that list. We can assume that all empty cells before that index have been filled in, and the others are still to do.
Instead of calling is_valid on each digit, collect the valid moves -- an idea you tried to implement in the second version.
Here is the improved version:
def valid_moves(grid, x, y):
ybox = y - y % 3
xbox = x - x % 3
return set(range(1, 10)).difference(
grid[x]
).difference(
(row[y] for row in grid)
).difference(
(num for row in grid[xbox: xbox + 3]
for num in row[ybox: ybox + 3])
)
def solve(grid, empty_cells, i=0):
if i >= len(empty_cells):
return grid
x, y = empty_cells[i]
i += 1
for n in valid_moves(grid, x, y):
grid[x][y] = n
if solve(grid, empty_cells, i):
return grid
grid[x][y] = 0
I'm making a sudoku solving program in Python.
I have a class, an inner class, a list, constructors and a function that would make the list of Row objects.
class Table:
rows = []
def __init__(self):
initRows()
class Row:
numbers = []
def __init__(self):
"stuff that stores a list of integers in **numbers** list"
def initRows(self, numbers):
for i in range(9):
temp_row = self.Row(i, numbers)
self.rows.append(temp_row)
The program goes like this:
When a Table object is created it automatically tries to make a 9 length list of Row objects with the initRows() function.
In the initRows() function we just create a temporary object of class Row and instantly append() it to the rows list.
When we create a Row object it just stores the given numbers for the given row of the sudoku table.
If I print() the numbers list of each temporary Row object after it is created then it gives the correct values.
[0, 0, 0, 7, 4, 0, 0, 0, 6]
[4, 0, 6, 8, 0, 0, 5, 0, 7]
[7, 0, 0, 0, 9, 0, 0, 0, 4]
[0, 3, 0, 9, 8, 4, 7, 0, 0]
[8, 2, 0, 6, 1, 3, 4, 0, 9]
[0, 4, 0, 0, 0, 0, 3, 0, 0]
[0, 6, 2, 3, 7, 0, 0, 0, 5]
[0, 0, 5, 4, 0, 9, 0, 0, 0]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
But when I try to print() the values of each Row object after the for loop loops once or more or after the initialization is finished then the list is filled with only the last Row object
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
[0, 7, 0, 0, 6, 1, 2, 0, 8]
I was searching on the internet for hours and found that people have issues with the append() function of the list class but nothing helped.
So my question is: How could I make this work?
(If any other information/part of code is needed: ask away!)
It turns out in the Row class the numbers list was a class attribute (means it's shared across all objects of the Row class) rather than an instance attribute.
I have a pandas series in python.
Is there a function/easy way to construct a series which contains the number of appearances of given values?
For demonstration, suppose I have the following Series: 1, 3, 1, 5, 10.
I want to count how many appearances each value has, from the following list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
The series that should return is 2, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1
We do value_counts + reindex
l=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
l1=[1, 3, 1, 5, 10]
pd.Series(l1).value_counts().reindex(l,fill_value=0).tolist()
[2, 0, 1, 0, 1, 0, 0, 0, 0, 1]
Use numpy.bincount
import numpy as np
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
s = pd.Series([1, 3, 1, 5, 10])
out = list(np.bincount(s)[[l]])
out
[2, 0, 1, 0, 1, 0, 0, 0, 0, 1]
With map:
s = pd.Series([1, 3, 1, 5, 10])
inp_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pd.Series(inp_list).map(s.value_counts()).fillna(0).astype(int).tolist()
Or list comp with get
c = s.value_counts()
[c.get(i,0) for i in inp_list]
#or [*map(lambda x: c.get(x,0),inp_list)]
[2, 0, 1, 0, 1, 0, 0, 0, 0, 1]
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I wrote a backtracking Sudoku solving algorithm in Python.
It solves a 2D array like this (zero means "empty field"):
[
[7, 0, 0, 0, 0, 9, 0, 0, 3],
[0, 9, 0, 1, 0, 0, 8, 0, 0],
[0, 1, 0, 0, 0, 7, 0, 0, 0],
[0, 3, 0, 4, 0, 0, 0, 8, 0],
[6, 0, 0, 0, 8, 0, 0, 0, 1],
[0, 7, 0, 0, 0, 2, 0, 3, 0],
[0, 0, 0, 5, 0, 0, 0, 1, 0],
[0, 0, 4, 0, 0, 3, 0, 9, 0],
[5, 0, 0, 7, 0, 0, 0, 0, 2],
]
like this:
[
[7, 5, 8, 2, 4, 9, 1, 6, 3],
[4, 9, 3, 1, 5, 6, 8, 2, 7],
[2, 1, 6, 8, 3, 7, 4, 5, 9],
[9, 3, 5, 4, 7, 1, 2, 8, 6],
[6, 4, 2, 3, 8, 5, 9, 7, 1],
[8, 7, 1, 9, 6, 2, 5, 3, 4],
[3, 2, 7, 5, 9, 4, 6, 1, 8],
[1, 8, 4, 6, 2, 3, 7, 9, 5],
[5, 6, 9, 7, 1, 8, 3, 4, 2]
]
But for "hard" Sudokus (where there are a lot of zeros at the beginning), it's quite slow. It takes the algorithm around 9 seconds to solve the Sudoku above. That's a lot better then what I startet with (90 seconds), but still slow.
I think that the "deepcopy" can somehow be improved/replaced (because it is executed 103.073 times in the example below), but my basic approaches were slower..
I heard of 0.01 second C/C++ solutions but I'm not sure if those are backtracking algorithms of some kind of mathematical solution...
This is my whole algorithm with 2 example Sudokus:
from copy import deepcopy
def is_sol_row(mat,row,val):
m = len(mat)
for i in range(m):
if mat[row][i] == val:
return False
return True
def is_sol_col(mat,col,val):
m = len(mat)
for i in range(m):
if mat[i][col] == val:
return False
return True
def is_sol_block(mat,row,col,val):
rainbow = [0,0,0,3,3,3,6,6,6]
i = rainbow[row]
j = rainbow[col]
elements = {
mat[i + 0][j + 0], mat[i + 1][j + 0], mat[i + 2][j + 0],
mat[i + 0][j + 1], mat[i + 1][j + 1], mat[i + 2][j + 1],
mat[i + 0][j + 2], mat[i + 1][j + 2], mat[i + 2][j + 2],
}
if val in elements:
return False
return True
def is_sol(mat,row,col,val):
return is_sol_row(mat,row,val) and is_sol_col(mat,col,val) and is_sol_block(mat,row,col,val)
def findAllZeroIndizes(mat):
m = len(mat)
indizes = []
for i in range(m):
for j in range(m):
if mat[i][j] == 0:
indizes.append((i,j))
return indizes
def sudoku(mat):
q = [(mat,0)]
zeroIndizes = findAllZeroIndizes(mat)
while q:
t,numSolvedIndizes = q.pop()
if numSolvedIndizes == len(zeroIndizes):
return t
else:
i,j = zeroIndizes[numSolvedIndizes]
for k in range(1,10):
if is_sol(t,i,j,k):
newt = deepcopy(t)
newt[i][j] = k
q.append((newt,numSolvedIndizes+1))
return False
mat = [
[7, 0, 0, 0, 0, 9, 0, 0, 3],
[0, 9, 0, 1, 0, 0, 8, 0, 0],
[0, 1, 0, 0, 0, 7, 0, 0, 0],
[0, 3, 0, 4, 0, 0, 0, 8, 0],
[6, 0, 0, 0, 8, 0, 0, 0, 1],
[0, 7, 0, 0, 0, 2, 0, 3, 0],
[0, 0, 0, 5, 0, 0, 0, 1, 0],
[0, 0, 4, 0, 0, 3, 0, 9, 0],
[5, 0, 0, 7, 0, 0, 0, 0, 2],
]
# mat = [
# [3, 0, 6, 5, 0, 8, 4, 0, 0],
# [5, 2, 0, 0, 0, 0, 0, 0, 0],
# [0, 8, 7, 0, 0, 0, 0, 3, 1],
# [0, 0, 3, 0, 1, 0, 0, 8, 0],
# [9, 0, 0, 8, 6, 3, 0, 0, 5],
# [0, 5, 0, 0, 9, 0, 6, 0, 0],
# [1, 3, 0, 0, 0, 0, 2, 5, 0],
# [0, 0, 0, 0, 0, 0, 0, 7, 4],
# [0, 0, 5, 2, 0, 6, 3, 0, 0]
# ]
print(sudoku(mat))
The largest time sink is that, for every open position, you try each of the nine digits, without learning anything about the attempts. Your test grid has 56 open grid locations, so anything you do is magnified through that lens. A little preprocessing will go a long way. For instance, make a list of available numbers in each row and column. Key that appropriately, and use that for your search instead of range(m).
Another technique is to apply simple algorithms to make trivial placements as they become available. For instance, you can quickly derive the 1 in the upper-left block, and the missing 7s in the left and middle columns of blocks. This alone cuts the solution time in half. Wherever you're down to a single choice for what number goes in a selected open square, or where a selected number can be placed in a particular row/col/block, then make that placement before you engage in exhaustive backtracking.
data = [[0, 1, 1, 5, 5, 5, 0, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6, 6, 6, 6, 6],
[1, 1, 1, 0, 5, 5, 5, 0, 2, 2, 0, 0, 2, 0, 0, 6, 6, 6, 0, 0, 6, 6],
[1, 1, 1, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 0, 0, 2, 6, 0, 0, 6, 6]]
The data object i have is a <class 'numpy.ndarray'>
Knowing data is a numpy object i did the following:
data = np.array(data)
i want to set the numbers inside a list i give as input to 0, what i tried:
data[~np.isin(data,[2,4])] = 0
i expect all the 2 and 4 occurrences in the previous matrix to be 0 and the rest to keep their values, what i got:
TypeError: only integer scalar arrays can be converted to a scalar index
also tried to give data as a numpy array using np.array gave error as well.
You should not negate the mask from np.isin check if you intend to set those matching values to 0. The below code works just fine:
Also, you should make the data a numpy array instead of list of lists.
In [10]: data = np.array([[0, 1, 1, 5, 5, 5, 0, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6, 6, 6, 6, 6],
...: [1, 1, 1, 0, 5, 5, 5, 0, 2, 2, 0, 0, 2, 0, 0, 6, 6, 6, 0, 0, 6, 6],
...: [1, 1, 1, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 0, 0, 2, 6, 0, 0, 6, 6]])
...:
In [11]: data[np.isin(data, [2, 4])] = 0
In [12]: data
Out[12]:
array([[0, 1, 1, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6],
[1, 1, 1, 0, 5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 0, 0, 6, 6],
[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 6, 6]])
Just to reproduce your error:
In [13]: data = [[0, 1, 1, 5, 5, 5, 0, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6, 6, 6, 6, 6],
...: [1, 1, 1, 0, 5, 5, 5, 0, 2, 2, 0, 0, 2, 0, 0, 6, 6, 6, 0, 0, 6, 6],
...: [1, 1, 1, 0, 0, 0, 0, 0, 2, 2, 0, 2, 2, 2, 0, 0, 2, 6, 0, 0, 6, 6]]
...:
In [14]: data[np.isin(data, [2, 4])] = 0
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-14-06ee1662f1f2> in <module>()
----> 1 data[np.isin(data, [2, 4])] = 0
TypeError: only integer scalar arrays can be converted to a scalar index