How can I improve performance of Sudoku solver? - python

I can't improve the performance of the following Sudoku Solver code. I know there are 3 loops here and they probably cause slow performance but I can't find a better/more efficient way. "board" is mutated with every iteration of recursion - if there are no zeros left, I just need to exit the recursion.
I tried to isolate "board" from mutation but it hasn't changed the performance. I also tried to use list comprehension for the top 2 "for" loops (i.e. only loop through rows and columns with zeros), tried to find coordinates of all zeros, and then use a single loop to go through them - hasn't helped.
I think I'm doing something fundamentally wrong here with recursion - any advice or recommendation on how to make the solution faster?
def box(board,row,column):
start_col = column - (column % 3)
finish_col = start_col + 3
start_row = row - (row % 3)
finish_row = start_row + 3
return [y for x in board[start_row:finish_row] for y in x[start_col:finish_col]]
def possible_values(board,row,column):
values = {1,2,3,4,5,6,7,8,9}
col_values = [v[column] for v in board]
row_values = board[row]
box_values = box(board, row, column)
return (values - set(row_values + col_values + box_values))
def solve(board, i_row = 0, i_col = 0):
for rn in range(i_row,len(board)):
if rn != i_row: i_col = 0
for cn in range(i_col,len(board)):
if board[rn][cn] == 0:
options = possible_values(board, rn, cn)
for board[rn][cn] in options:
if solve(board, rn, cn):
return board
board[rn][cn] = 0
#if no options left for the cell, go to previous cell and try next option
return False
#if no zeros left on the board, problem is solved
return True
problem = [
[9, 0, 0, 0, 8, 0, 0, 0, 1],
[0, 0, 0, 4, 0, 6, 0, 0, 0],
[0, 0, 5, 0, 7, 0, 3, 0, 0],
[0, 6, 0, 0, 0, 0, 0, 4, 0],
[4, 0, 1, 0, 6, 0, 5, 0, 8],
[0, 9, 0, 0, 0, 0, 0, 2, 0],
[0, 0, 7, 0, 3, 0, 2, 0, 0],
[0, 0, 0, 7, 0, 5, 0, 0, 0],
[1, 0, 0, 0, 4, 0, 0, 0, 7]
]
solve(problem)

Three things you can do to speed this up:
Maintain additional state using arrays of integers to keep track of row, col, and box candidates (or equivalently values already used) so that finding possible values is just possible_values = row_candidates[row] & col_candidates[col] & box_candidates[box]. This is a constant factors thing that will change very little in your approach.
As kosciej16 suggested use the min-remaining-values heuristic for selecting which cell to fill next. This will turn your algorithm into crypto-DPLL, giving you early conflict detection (cells with 0 candiates), constraint propagation (cells with 1 candidate), and a lower effective branching factor for the rest.
Add logic to detect hidden singles (like the Norvig solver does). This will make your solver a little slower for the simplest puzzles, but it will make a huge difference for puzzles where hidden singles are important (like 17 clue puzzles).

A result that worked at the end thanks to 53x15 and kosciej16. Not ideal or most optimal but passes the required performance test:
def solve(board, i_row = 0, i_col = 0):
cells_to_solve = [((rn, cn), possible_values(board,rn,cn)) for rn in range(len(board)) for cn in range(len(board)) if board[rn][cn] == 0]
if not cells_to_solve: return True
min_n_of_values = min([len(x[1]) for x in cells_to_solve])
if min_n_of_values == 0: return False
best_cells_to_try = [((rn,cn),options) for ((rn,cn),options) in cells_to_solve if len(options) == min_n_of_values]
for ((rn,cn),options) in best_cells_to_try:
for board[rn][cn] in options:
if solve(board, rn, cn):
return board
board[rn][cn] = 0
return False

Related

Sorting an iterator in python

I want to iterate over a big itertools product, but I want to do it in a different order from the one that product offers. The problem is that sorting an iterator using sorted takes time. For example:
from itertools import product
import time
RNG = 15
RPT = 6
start = time.time()
a = sorted(product(range(RNG), repeat=RPT), key=sum)
print("Sorted: " + str(time.time() - start))
print(type(a))
start = time.time()
a = product(range(RNG), repeat=RPT)
print("Unsorted: " + str(time.time() - start))
print(type(a))
Creating the sorted iterator takes about twice as long. I'm guessing this is because sorted actually involves going through the whole iterator and returning a list. Whereas the second unsorted iterator is doing some sort of lazy evaluation magic.
I guess there's really two questions here.
General question: is there a lazy evaluation way to change the order items appear in an iterator?
Specific question: is there a way to loop through all m-length lists of ints less than n, hitting lists with smaller sums first?
If your objective is to reduce memory consumption, you could write your own generator to return the permutations in order of their sum (see below). But, if memory is not a concern, sorting the output of itertools.product() will be faster than the Python code that produces the same result.
Writing a recursive function that produces the combinations of values in order of their sum can be achieved by merging multiple iterators (one per starting value) based on the smallest sum:
def sumCombo(A,N):
if N==1:
yield from ((n,) for n in A) # single item combos
return
pA = [] # list of iterator/states
for i,n in enumerate(A): # for each starting value
ip = sumCombo(A[i:],N-1) # iterator recursion to N-1
p = next(ip) # current N-1 combination
pA.append((n+sum(p),p,n,ip)) # sum, state & iterator
while pA:
# index and states of smallest sum
i,(s,p,n,ip) = min(enumerate(pA),key=lambda ip:ip[1][0])
ps = s
while s == ps: # output equal sum combinations
yield (n,*p) # yield starting number with recursed
p = next(ip,None) # advance iterator
if p is None:
del pA[i] # remove exhausted iterators
break
s = n+sum(p) # compute new sum
pA[i] = (s,p,n,ip) # and update states
This will only produce combinations of values as opposed to the product which produces distinct permutations of these combinations. (38,760 combinations vs 11,390,625 products).
In order to obtain all the products, you would need to run these combinations through a function that generates distinct permutations:
def permuteDistinct(A):
if len(A) == 1:
yield tuple(A) # single value
return
seen = set() # track starting value
for i,n in enumerate(A): # for each starting value
if n in seen: continue # not yet used
seen.add(n)
for p in permuteDistinct(A[:i]+A[i+1:]):
yield (n,*p) # starting value & rest
def sumProd(A,N):
for p in sumCombo(A,N): # combinations in order of sum
yield from permuteDistinct(p) # permuted
So sumProd(range(RNG),RPT) will produce the 11,390,625 permutations in order of their sum, without storing them in a list BUT it will take 5 times longer to do so (compared to sorting the product).
a = sorted(product(range(RNG), repeat=RPT), key=sum) # 4.6 sec
b = list(sumProd(range(RNG),RPT)) # 23 sec
list(map(sum,a)) == list(map(sum,b)) # True (same order of sums)
a == b # False (order differs for equal sums)
a[5:15] b[5:15] sum
(0, 1, 0, 0, 0, 0) (0, 1, 0, 0, 0, 0) 1
(1, 0, 0, 0, 0, 0) (1, 0, 0, 0, 0, 0) 1
(0, 0, 0, 0, 0, 2) (0, 0, 0, 0, 0, 2) 2
(0, 0, 0, 0, 1, 1) (0, 0, 0, 0, 2, 0) 2
(0, 0, 0, 0, 2, 0) (0, 0, 0, 2, 0, 0) 2
(0, 0, 0, 1, 0, 1) (0, 0, 2, 0, 0, 0) 2
(0, 0, 0, 1, 1, 0) (0, 2, 0, 0, 0, 0) 2
(0, 0, 0, 2, 0, 0) (2, 0, 0, 0, 0, 0) 2
(0, 0, 1, 0, 0, 1) (0, 0, 0, 0, 1, 1) 2
(0, 0, 1, 0, 1, 0) (0, 0, 0, 1, 0, 1) 2
If your process is searching for specific sums, it may be interesting to filter on combinations first and only expand distinct permutations for the combinations (sums) that meet your criteria. This could potentially cut down the number of iterations considerably (sumCombo(range(RNG),RPT) # 0.22 sec is faster than sorting the products).

Python sudoku backtracking

I was trying out the backtracking algorithm with an easy example (sudoku). I first tried another approach where more possibilities are canceled, but after I got the same error I switched to an easier solution.
look for the first unsolved spot
fill in every number between 1 and 9 and backtrack the new field if it is valid
When I run it and output the non-valid fields I can see that when the algorithm goes out of a recursion call the spot that was in that recursion call is still a 9 (so the algorithm couldn't find anything for that spot)
e.g. the first two lines look something like this (it's trying to solve an empty field):
[1, 2, 3, 4, 6, 9, 9, 9, 9]
[9, 9, 9, 9, 9, 9, 9, 0, 0]
I thought it was a reference error and inserted
[e for e in field]
in the backtracking call so that the old field doesn't get altered although that didn't seem to help.
Here is my code:
for i in range(9):
a = [field[i][j] for j in range(9) if field[i][j] != 0]
if len(a) != len(set(a)):
return False
for i in range(9):
a = [field[j][i] for j in range(9) if field[j][i] != 0]
if len(a) != len(set(a)):
return False
for x in range(3):
for y in range(3):
a = []
for addX in range(3):
for addY in range(3):
spot = field[x * 3 + addX][y * 3 + addY]
if spot != 0:
a.append(spot)
if len(a) != len(set(a)):
return False
return True
def findEmpty(field):
for i in range(9):
for j in range(9):
if field[i][j] == 0:
return i, j
def backtracking(field):
find = findEmpty(field)
if not find:
return True, field
else:
x, y = find
for i in range(1, 10):
print(f"Trying {i} at {x} {y}")
field[x][y] = i
if isValid(field):
s = backtracking([e for e in field])
if s[0]:
return s
else:
print("Not valid")
for row in field:
print(row)
return False, None
field = [[0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]]
solution = backtracking(field)
if solution[0]:
print("There was a solution. The field is:")
for row in solution[1]:
print(row)
else:
print("No solution was found")
Okay, so based on what I can see in the logs, what happens is that when the code gets to 9 and still does not get an answer, it will backtrack, but keeps the value at 9.
So what happens is, every single time the program backtracks, it leaves the value at 9 and then go to the previous value, which might also go to 9, which is invalid as the value we backtracked from is already a 9. This causes a cycle where the program would backtrack straight to the start and make most slots 9, as you can see in your example.
So the solution would be to add a few lines to backtrack() as below. In short, that extra 2 lines checks if the invalid answer is a 9, if it is, it is resetted to a 0 and backtracks to the previous value until it gets a valid answer.
def backtracking(field):
find = findEmpty(field)
if not find:
return True, field
else:
x, y = find
for i in range(1, 10):
print(f"Trying {i} at {x} {y}")
field[x][y] = i
if isValid(field):
s = backtracking(field)
if s[0]:
return s
else:
print("Not valid")
if field[x][y] == 9:
field[x][y] = 0
for row in field:
print(row)
return False, None
Solution it gave:
[2, 3, 4, 5, 1, 6, 7, 8, 9]
[1, 5, 6, 7, 8, 9, 2, 3, 4]
[7, 8, 9, 2, 3, 4, 1, 5, 6]
[3, 1, 2, 4, 5, 7, 6, 9, 8]
[4, 6, 5, 1, 9, 8, 3, 2, 7]
[8, 9, 7, 3, 6, 2, 4, 1, 5]
[5, 2, 8, 6, 4, 1, 9, 7, 3]
[6, 7, 3, 9, 2, 5, 8, 4, 1]
[9, 4, 1, 8, 7, 3, 5, 6, 2]
I did some research and apparently it really is a reference error. For me importing pythons copy library and assigning each new field saying
f = copy.deepcopy(field)
fixes the issue (this also works for the complex example).
1- Create rows, cols, and boxes (3x3 units) array of dictionaries to store which indices of each row, col, and boxes have numbers.
2- Take a screenshot of the board. Run a for-loop and mark which points include numbers.
3- Call the recursive backtrack function.
4- Always in recursive functions define the base case to exit out of the recursion.
5- Start a for-loop to see which coordinate is ".". If you see ".", apply steps:[6,7,8,9]
6- Now we need to insert a valid number here. A valid number is a number that does not exist in the current row, col, and box.
7- If you find a valid number, insert it into the board and update rows, cols, and boxes.
8- After we inserted the valid point, we call backtrack function for the next ".".
9- When calling the backtrack, decide at which point you are. If you are in the last column, your next backtrack function will start from the next row and column 0. But if you are in the middle of row, you just increase the column parameter for next backtrack function.
10- If in step 5 your value is not ".", that means there is already a valid number here so call next backtracking depending on where position is. If you are in the last column, your next backtrack function will start from the next row and column 0. But if you are in the middle of row, you just increase the column parameter for next backtrack function.
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
n=len(board)
# create state variables,keep track of rows, cols and boxes
rows=[{} for _ in range(n)]
cols=[{} for _ in range(n)]
boxes=[{} for _ in range(n)]
# get the initial state of the grid
for r in range(n):
for c in range(n):
if board[r][c]!='.':
val=board[r][c]
box_id=self.get_box_id(r,c)
boxes[box_id][val]=True
rows[r][val]=True
cols[c][val]=True
# this backtracking just tells if shoul move to the next cell or not
self.backtrack(board,boxes,rows,cols,0,0)
def backtrack(self,board,boxes,rows,cols,r,c):
# base case. If I hit the last row or col, means all digits were correct so far
if r>=len(board) or c>=len(board[0]):
return True
# situation when cell is empty, fill it with correct value
if board[r][c]==".":
for num in range(1,10):
box_id=self.get_box_id(r,c)
box=boxes[box_id]
row=rows[r]
col=cols[c]
str_num=str(num)
# check rows, cols and boxes make sure str_num is not used before
if self.is_valid(box,col,row,str_num):
board[r][c]=str_num
boxes[box_id][str_num]=True
cols[c][str_num]=True
rows[r][str_num]=True
# if I am the last col and I placed the correct val, move to next row. So first col of the next row
if c==len(board)-1:
if self.backtrack(board,boxes,rows,cols,r+1,0):
return True
# if I am in col between 0-8, move to the col+1, in the same row
else:
if self.backtrack(board,boxes,rows,cols,r,c+1):
return True
# If I got a wrong value, then backtrack. So clear the state that you mutated
del box[str_num]
del row[str_num]
del col[str_num]
board[r][c]="."
# if cell is not empty just call the next backtracking
else:
if c==len(board)-1:
if self.backtrack(board,boxes,rows,cols,r+1,0):
return True
else:
if self.backtrack(board,boxes,rows,cols,r,c+1):
return True
return False
def is_valid(self,box,row,col,num):
if num in box or num in row or num in col:
return False
else:
return True
# a helper to get the id of the 3x3 sub grid, given row and column
def get_box_id(self,r,c):
row=(r//3)*3
col=c//3
return row+col

Google Foobar solution works on Jupyter notebook but not on Google's terminal

I'm on level 3 of Google Foobar, and the code I've written works in a Jupyter notebook, but when I run it in the Foobar command line none of the test cases pass. There's no error of any kind when I run it in Foobar, it just says the answer is incorrect.
Doomsday Fuel
=============
Making fuel for the LAMBCHOP's reactor core is a tricky process because of the exotic matter involved. It starts as raw ore, then during processing, begins randomly changing between forms, eventually reaching a stable form. There may be multiple stable forms that a sample could ultimately reach, not all of which are useful as fuel.
Commander Lambda has tasked you to help the scientists increase fuel creation efficiency by predicting the end state of a given ore sample. You have carefully studied the different structures that the ore can take and which transitions it undergoes. It appears that, while random, the probability of each structure transforming is fixed. That is, each time the ore is in 1 state, it has the same probabilities of entering the next state (which might be the same state). You have recorded the observed transitions in a matrix. The others in the lab have hypothesized more exotic forms that the ore can become, but you haven't seen all of them.
Write a function solution(m) that takes an array of array of nonnegative ints representing how many times that state has gone to the next state and return an array of ints for each terminal state giving the exact probabilities of each terminal state, represented as the numerator for each state, then the denominator for all of them at the end and in simplest form. The matrix is at most 10 by 10. It is guaranteed that no matter which state the ore is in, there is a path from that state to a terminal state. That is, the processing will always eventually end in a stable state. The ore starts in state 0. The denominator will fit within a signed 32-bit integer during the calculation, as long as the fraction is simplified regularly.
For example, consider the matrix m:
[
[0,1,0,0,0,1], # s0, the initial state, goes to s1 and s5 with equal probability
[4,0,0,3,2,0], # s1 can become s0, s3, or s4, but with different probabilities
[0,0,0,0,0,0], # s2 is terminal, and unreachable (never observed in practice)
[0,0,0,0,0,0], # s3 is terminal
[0,0,0,0,0,0], # s4 is terminal
[0,0,0,0,0,0], # s5 is terminal
]
So, we can consider different paths to terminal states, such as:
s0 -> s1 -> s3
s0 -> s1 -> s0 -> s1 -> s0 -> s1 -> s4
s0 -> s1 -> s0 -> s5
Tracing the probabilities of each, we find that
s2 has probability 0
s3 has probability 3/14
s4 has probability 1/7
s5 has probability 9/14
So, putting that together, and making a common denominator, gives an answer in the form of
[s2.numerator, s3.numerator, s4.numerator, s5.numerator, denominator] which is
[0, 3, 2, 9, 14].
Languages
=========
To provide a Java solution, edit Solution.java
To provide a Python solution, edit solution.py
Test cases
==========
Your code should pass the following test cases.
Note that it may also be run against hidden test cases not shown here.
-- Java cases --
Input:
Solution.solution({{0, 2, 1, 0, 0}, {0, 0, 0, 3, 4}, {0, 0, 0, 0, 0}, {0, 0, 0, 0,0}, {0, 0, 0, 0, 0}})
Output:
[7, 6, 8, 21]
Input:
Solution.solution({{0, 1, 0, 0, 0, 1}, {4, 0, 0, 3, 2, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}})
Output:
[0, 3, 2, 9, 14]
-- Python cases --
Input:
solution.solution([[0, 2, 1, 0, 0], [0, 0, 0, 3, 4], [0, 0, 0, 0, 0], [0, 0, 0, 0,0], [0, 0, 0, 0, 0]])
Output:
[7, 6, 8, 21]
Input:
solution.solution([[0, 1, 0, 0, 0, 1], [4, 0, 0, 3, 2, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]])
Output:
[0, 3, 2, 9, 14]
My solution is as follows:
import numpy as np
import pandas as pd
from fractions import Fraction
def solution(m):
if m == [[0]]:
return [1, 1]
else:
return run_matrix_computation(m)
def run_matrix_computation(starting_ore_matrix):
numpy_matrix = convert_to_numpy_matrix(starting_ore_matrix)
ordered_matrix = order_matrix(numpy_matrix)
absorption_matrix, split_index = create_absorption_matrix(ordered_matrix)
R, Q = store_R_and_Q(absorption_matrix, split_index)
FR = compute_FR(R, Q)
first_row = get_first_row_of_FR(FR)
common_denominator, fraction_list = calculate_common_denominator(first_row)
return return_int_array(common_denominator, fraction_list)
def convert_to_numpy_matrix(original_matrix):
return np.asarray(original_matrix)
def order_matrix(numpy_matrix):
labeled_dataframe = pd.DataFrame(data=numpy_matrix)
index_order = labeled_dataframe.sum(axis=1).sort_values(ascending=True).index
converted_matrix = convert_matrix_to_fractions(labeled_dataframe)
return converted_matrix.iloc[index_order, index_order]
def convert_matrix_to_fractions(original_matrix):
for i in range(len(original_matrix.index)):
sum = original_matrix.sum(axis=1).iloc[i]
if sum != 0:
for j in range(len(original_matrix.columns)):
if original_matrix.iloc[i, j]:
original_matrix.iloc[i, j] = original_matrix.iloc[i, j] / sum
return original_matrix
def create_absorption_matrix(sorted_matrix):
count = 0
for i in range(len(sorted_matrix.index)):
if not(sorted_matrix.sum(axis=1).iloc[i]):
sorted_matrix.iloc[i, i] = 1
count = count + 1
return sorted_matrix, count
def store_R_and_Q(absorption_matrix, split_index):
return split_into_new_matrices(absorption_matrix, split_index)
def split_into_new_matrices(absorption_matrix, split_index):
numpy_matrix = absorption_matrix.to_numpy()
R = numpy_matrix[split_index:, :split_index]
Q = numpy_matrix[split_index:, split_index:]
return R, Q
def calculate_F(R, Q):
num_rows, num_cols = Q.shape
I = np.identity(num_rows)
IQ = I - Q
return np.linalg.inv(IQ)
def compute_FR(R, Q):
F = calculate_F(R, Q)
return np.matmul(F, R)
def get_first_row_of_FR(FR):
return FR[0, :]
def calculate_common_denominator(list):
fraction_list = convert_to_fractions(list)
list_denominators = get_denominators(fraction_list)
GCD = calculate_greatest_common_denominator(list_denominators)
return GCD, fraction_list
def convert_to_fractions(list):
fraction_list = []
for i in range(len(list)):
fraction_list.append(Fraction(list[i]).limit_denominator())
return fraction_list
def get_denominators(fractions):
denominators = []
for i in range(len(fractions)):
denominators.append(fractions[i].denominator)
return denominators
def calculate_greatest_common_denominator(denominators):
GCD = 0
if len(denominators) == 1:
return denominators
else:
for i in range(len(denominators) - 1):
cur_GCD = np.lcm(denominators[i], denominators[i + 1])
if cur_GCD > GCD:
GCD = cur_GCD
return GCD
def return_int_array(denominator, fractions):
final_list = []
for i in range(len(fractions)):
if(not fractions[i].numerator):
final_list.append(0)
else:
multiplier = denominator/fractions[i].denominator
final_list.append(int(fractions[i].numerator * multiplier))
final_list.append(int(denominator))
return final_list
Running any of the test cases with this code works, yet every test fails on Foobar. Is there some sort of formatting error? I've examined the type of object that's returned vs the type of object that Foobar is looking for and they're both int lists. Everything in my code is, to my knowledge, supported by Python 2.7.13 which is what Foobar uses. The libraries I used were also allowed.
I think you should try to check the libraries first, if they are installed and the versions.
Then you should use replit or something else with the exact environment.
And finally, is your code on solution class?
I am late to this but I believe the problem lies in how python 2.7 handles division. It was fixed for me by putting this at the start of my file.
from __future__ import division

Function is returning type None when I expect an array

I'm trying to returning an array that has been passed through a recursive function. But I'm only getting back None, instead of the bo Numpy array which I expected.
import numpy as np
board = [0, 0, 4, 5, 1, 9, 0, 0, 0, 6, 1, 0, 0, 7, 0, 0, 5, 9, 0, 0, 0, 6, 3, 0, 0, 0, 0, 5,
4, 0, 3, 0, 0, 0, 0, 2, 3, 0, 6, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, 0, 6, 7, 0, 0, 2, 0,
0, 0, 4, 0, 5, 8, 0, 0, 0, 4, 0, 0, 2, 0, 4, 0, 0, 1, 0, 0, 7, 0, 6]
grid = np.asarray(board).reshape(9, 9)
def possible(bo, y, x, n):
for i in range(0, 9):
if bo[y][i] == n:
return False
for i in range(0, 9):
if bo[i][x] == n:
return False
x0 = (x // 3) * 3
y0 = (y // 3) * 3
for i in range(0, 3):
for j in range(0, 3):
if bo[y0 + i][x0 + j] == n:
return False
return True
def solve(bo):
for y in range(9): # row
for x in range(9): # column
if bo[y][x] == 0:
for n in range(1, 10):
if possible(bo, y, x, n):
bo[y][x] = n
solve(bo)
bo[y][x] = 0
return
return bo
solved = solve(grid)
print(solved)
Why is this?
I can print it out to the console. But the return value is None.
Just to note, the first return statement exits out of the recursive function. Allowing to move onto subsequent items in the array.
(Previous question was marked as duplicate - but the linked question didn't answer my query)
Edit: To make it clear what i'm trying to do. I'm trying to return the modified bo array. The first return statement exits out of the recursive for n in range statement. And then runs through with another # in the range.
I believe your problem is in your return statement in your solve(bo) function.
for n in range(1, 10):
if possible(bo, y, x, n):
bo[y][x] = n
solve(bo)
bo[y][x] = 0
return
Whenever your function finishes this loop, it will always return None because it has a return, but isn't returning anything.
If I change the bare return line to:
return 'foobar'
the result is:
In [5]: grid = np.asarray(board).reshape(9, 9)
In [6]: solve(grid)
Out[6]: 'foobar'
A bare return is the same as return None (as is falling off the end of the function).
Even if the function is recursive, and modifies bo (and grid after running it has the right values), the function returns on that condition, not the final return bo line.
So
return bo
in both places returns the modified grid.
def solve(bo):
for y in range(9): # row
for x in range(9): # column
if bo[y][x] == 0:
for n in range(1, 10):
if possible(bo, y, x, n):
bo[y][x] = n
solve(bo) # doesn't use the return value
bo[y][x] = 0
return bo # final return
return # bo optional
Within the recursion you count on bo being modified, and never use the returned value. That's why with solve(grid), grid is changed. If you want solve to return the modfied grid you have to return it explicitly.
In code like this, you need to pay close attention to all return statements, and be aware of which is returning when.

Python Sudoku Reduce the number of for loops

I am trying to solve the sudoku problem and I need to add all the tuples that satisfy the constraint.
Here is the problem,
The variables contain non-repeated number from 0 to 9 in sorted order. The domain of each variable can be different.
Eg:
v1 = [1,2,3,4]
v2 = [3,4,6,7]
v3 = [3,5,7,8,9]
...
v9 = [1,2,3,4,5,6,7,8,9]
I want all sets of number where there is no repeated number,
I have come up with the following cumbersome and slow algorithm, but is there a better way to do this?
for d0 in v1:
for d1 in v2:
...
for d9 in v9:
if d0 != d1 and d0 != d2 and d0 != d3 ... d0 != d9
...
and d7 != d8 and d7 != d9:
and d8 != d9:
print(d0,d1,d2,d3,d4,d5,d6,d7,d8,d9)
Is there a more effective way of doing it without using 9 for loops and a long list of and statements??
I can't use the constraint solver like python-constraint since I need to implement it.
I wrote a sudoku solver a while back for fun...
I've posted the code for your reference. It's actually really efficient. I also explain it at the end.
#imports
import sys
import time
#test sudoku puzzle
a = [[0, 6, 0, 1, 3, 0, 0, 0, 8],
[0, 0, 2, 0, 0, 6, 0, 0, 0],
[1, 8, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 7, 0, 4, 2, 0, 0, 0],
[5, 0, 0, 0, 0, 0, 0, 0, 9],
[0, 0, 0, 5, 9, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 9, 2],
[0, 0, 0, 7, 0, 0, 3, 0, 0],
[7, 0, 0, 0, 1, 3, 0, 4, 0]]
'''
a = [[0, 0, 4, 0],
[0, 2, 0, 3],
[2, 0, 0, 0],
[0, 4, 0, 1]]
'''
#store all of the cells that have been solved already
solved = {}
for p in range(len(a)):
for q in range(len(a)):
if a[p][q] != 0:
solved[p, q] = 1
#denotes what direction the code is moving (if it is moving backwards, then we want the already solved cells to return, and if it is moving forwards, we want the solved cells to call next)
goingForward = True
#if the sudoku is solved or not
done = False
# number of iterations
counter = 0
#prints a sudoku neatly
def printSudoku(a):
for i in a:
array = ""
for j in i:
array += str(j) + " "
print array
def next(i, j, a):
global counter
global goingForward
global done
if done:
return
counter += 1
# gets the vertical in which a[i][j] is located
vertical = [z[j] for z in a]
# gets the "box" in which a[i][j] is located
box = []
x = (i/3 + 1)*3
y = (j/3 + 1)*3
for p in range(x-3, x):
for q in range(y-3, y):
box.append(a[p][q])
#if it already solved and it is going forward, call next
#else, return
if solved.has_key((i, j)):
if i == 8 and j == 8:
done = True
printSudoku(a)
return
if goingForward:
if j==8:
next(i+1, 0, a)
else:
next(i, j+1, a)
else:
return
#try every number for cell a[i][j]
#if none work, return so that the previous number may be changed
else:
for k in range(1, 10):
if k not in a[i] and k not in vertical and k not in box:
a[i][j] = k
if i == 8 and j == 8:
done = True
printSudoku(a)
return
if j==8:
goingForward = True
next(i+1, 0, a)
a[i][j] = 0
else:
goingForward = True
next(i, j+1, a)
a[i][j] = 0
goingForward = False
return
start_time = time.time()
#where we actually call the function
next(0, 0, a)
if done == False:
print "no solution!!"
print "It took " + str(time.time()-start_time) + " to solve this sudoku"
print "With " + str(counter) + " iterations"
This uses a technique called recursive backtracking. It starts at the very first cell and tries all possible values. If it finds a possible solution, then it keeps that value and goes on the next cell. For that cell, it employs the same method, trying all values from 1-9 until one works, then moving to the next cell. When it encounters a cell for which no values work, it gives up and goes to the previous cell, where it is determined that that particular value does not work and a new one must be used. The previous cell skips the errant value and goes to the next integer. Through a combination of backtracking and guessing, it eventually arrives at the correct answer.
It's also super speedy since a lot of the cases are skipped due to the error backtracking. For most "guessed" values, it does not get past the third or fourth cell.
I found the solution myself,
So freaking simple, just subtract the current value from the domain of the next list to prevent it from getting selected.
for d0 in v1:
for d1 in list(set(v2) - set(d0)):
...
for d9 in list(set(v9) - set(d1,d2,d3,d4,d5,d6,d7,d8,d9):
print(d0,d1,d2,d3,d4,d5,d6,d7,d8,d9)

Categories

Resources