Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended soduku solving #3034

Open
wants to merge 8 commits into
base: master
from
@@ -0,0 +1,201 @@
"""
Given a partially or empty grid, attempts to solve (n^2)*(n^2) sudoku,
where n is the box size
Eg:
sudoku 4*4 sudoku 9*9
box_size = n = 2 box_size = n = 3
+-------+-------+ +----------+----------+----------+
| 01 02 | 03 04 | | 01 02 03 | 04 05 08 | 09 06 07 |
| 03 04 | 01 02 | | 04 05 08 | 06 07 09 | 01 02 03 |
+-------+-------+ | 09 06 07 | 01 02 03 | 08 04 05 |
| 02 01 | 04 03 | +----------+----------+----------+
| 04 03 | 02 01 | | 02 01 09 | 08 03 04 | 05 07 06 |
+-------+-------+ | 03 08 04 | 05 06 07 | 02 01 09 |
| 05 07 06 | 09 01 02 | 03 08 04 |
+----------+----------+----------+
| 08 09 01 | 03 04 06 | 07 05 02 |
| 06 03 02 | 07 08 05 | 04 09 01 |
| 07 04 05 | 02 09 01 | 06 03 08 |
+----------+----------+----------+
"""


class Sudoku:
"""
Class object for sudoku
element 0 represents an empty element
"""

def __init__(self, box_size: int):
"""
initialize grid and box
"""
self.box_size = box_size
self.grid_size = box_size ** 2
self.grid = []
self.box = dict()
for r in range(self.grid_size):
self.grid.append([])
for c in range(self.grid_size):
self.grid[-1].append(0)
box_number = self.box_mapper(r, c)
if box_number not in self.box:
self.box[box_number] = {0}

def load_grid(self, arr: list) -> None:
"""
Load the values from arr into the grid
"""
if len(arr) != self.grid_size:
raise ValueError("Input grid size do not match")

for r in range(self.grid_size):
if len(arr[r]) != self.grid_size:
raise ValueError("Input grid size do not match")

for c in range(self.grid_size):
self.add_element(arr[r][c], r, c)

def box_mapper(self, row: int, column: int) -> int:
"""
Maps row,column into their respective boxes
"""
return (row // self.box_size) * 10 + (column // self.box_size)

def possible_values(self, row: int, column: int) -> set:
"""
Gets the possible values at row,column
"""
box_number = self.box_mapper(row, column)

possible_elements = set(range(1, (self.grid_size) + 1))
row_elements = set(self.grid[row])
column_elements = set([self.grid[i][column] for i in range(self.grid_size)])
box_elements = self.box[box_number]

return possible_elements - box_elements.union(row_elements, column_elements)

def next_empty(self, row: int, column: int) -> tuple:
"""
Returns the position of next empty element 0
if there exist a empty element
returns (True,row,column)
else
returns (False,-1,-1)
"""
(r, c) = (row, column)
while r < self.grid_size:
while c < self.grid_size:
if self.grid[r][c] == 0:
return (True, r, c)
c += 1
c = 0
r += 1
return (False, -1, -1)

def add_element(self, element: int, row: int, column: int) -> None:
"""
Adds element to grid[row][column]
"""
self.grid[row][column] = element
box_number = self.box_mapper(row, column)
self.box[box_number].add(element)

def remove_element(self, row: int, column: int) -> None:
"""
Removes element at grid[row][column]
"""
box_number = self.box_mapper(row, column)
element = self.grid[row][column]
self.box[box_number].remove(element)
self.grid[row][column] = 0
self.box[box_number].add(0)

def solver(self, row: int, column: int) -> bool:
"""
solve the next empty element from row,column
if the grid is complete
return true
else
return false
"""
(status, new_row, new_column) = self.next_empty(row, column)
if status is False:
return True

for element in self.possible_values(new_row, new_column):
self.add_element(element, new_row, new_column)
status = self.solver(new_row, new_column)
if status is True:
return True
self.remove_element(new_row, new_column)
return False

def solve(self) -> bool:
"""
Invoke solver and return true if the grid is solved
else return False
"""
return self.solver(0, 0)

def __str__(self):
string = ""
row_completer = ("+-" + "---" * self.box_size) * self.box_size + "+"
box_edge = [
x for x in range(self.grid_size) if x % self.box_size == self.box_size - 1
]

string += row_completer + "\n"
for r in range(self.grid_size):
string += "| "
for c in range(self.grid_size):
string += "%02d " % (self.grid[r][c])
if c in box_edge:
string += "| "
string += "\n"
if r in box_edge:
string += row_completer + "\n"

return string


if __name__ == "__main__":

accept_from_user = False
if accept_from_user:
box_size = int(input("Enter the box size: "))
question_grid = []
i = 1
print("Use zero to represent empty value")
while i <= box_size ** 2:
row_values = list(map(int, input(f"Enter the row {i} : ")))
if len(row_values) != box_size ** 2:
print(f"Row length should be {box_size ** 2}")
continue
question_grid.append(row_values)
i += 1
else:
box_size = 3
# a sample question grid
question_grid = [
[1, 0, 0, 0, 0, 7, 0, 9, 0],
[0, 3, 0, 0, 2, 0, 0, 0, 8],
[0, 0, 9, 6, 0, 0, 5, 0, 0],
[0, 0, 5, 3, 0, 0, 9, 0, 0],
[0, 1, 0, 0, 8, 0, 0, 0, 2],
[6, 0, 0, 0, 0, 4, 0, 0, 0],
[3, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 4, 0, 0, 0, 0, 0, 0, 7],
[0, 0, 7, 0, 0, 0, 3, 0, 0],
]
question = Sudoku(box_size)
question.load_grid(question_grid)

print("The initial question is ", question)
print("\nSolving...", end="")

result = question.solve()
if result is True:
print("Success. The answer is: ", question)
else:
print("Failed. Could not find a possible solution")
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.