a sudoku solver using ILP
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add simple GUI

+81 -4
+4 -1
README.md
··· 8 8 9 9 the `from_csv` and `from_json` class methods are provided to facilitate handling of input files. `from_array` handles input `numpy` arrays. the `solve` method performs the optimization and builds a 9x9 `numpy` array with the proposed solution. 10 10 11 + the [`gui.py`](/gui.py) script provides a simple GUI using [`tkinter`](https://docs.python.org/es/3.13/library/tkinter.html). run it to launch it. click on the grid to change the numbers; click on "Solve" to fill the sudoku. 12 + 13 + empty tiles are represented with zeros. 14 + 11 15 ## Dependencies 12 16 13 17 a pre-made environment can be set up with: ··· 22 26 23 27 run `solpoku.py` to solve the following sudoku three times: 24 28 a) hard-coded in the script, b) read from a [CSV](/test/sudoku.csv) file 25 - (here zeros represent empty tiles) 26 29 and c) read from a [JSON](/test/sudoku.json) file: 27 30 28 31 | | | | | | | | | |
+71
gui.py
··· 1 + import tkinter as tk 2 + import numpy as np 3 + from solpoku import SudokuProblem 4 + 5 + 6 + class Application(tk.Frame): 7 + 8 + def __init__(self, master=None): 9 + tk.Frame.__init__(self, master) 10 + self.grid() 11 + self.problem = SudokuProblem() 12 + self.init_number_grid() 13 + 14 + def solve(self): 15 + # Solve problem from array 16 + array = np.zeros((9, 9), dtype=np.uint8) 17 + for (i, j), value in self.grid_values.items(): 18 + array[i, j] = value.get() 19 + print(f"grid ({i+1},{j+1}) = {value.get()}") 20 + self.problem.set_objective_from_array(array) 21 + solution = self.problem.solve() 22 + # Fill grid 23 + for (i, j), value in self.grid_values.items(): 24 + value.set(solution[i, j]) 25 + # Update status 26 + self.status_label.config(text=self.problem._model.status) 27 + 28 + def _update_value_callback(self, row, col): 29 + def wrapped(): 30 + value = self.grid_values[(row, col)] 31 + if value.get() == 9: 32 + value.set(0) 33 + else: 34 + value.set(value.get() + 1) 35 + self.status_label.config(text="unsolved") 36 + return wrapped 37 + 38 + def init_number_grid(self): 39 + # Number grid 40 + self.grid_values = {} 41 + for i in range(9): 42 + for j in range(9): 43 + button_value = tk.IntVar() 44 + button_value.set(0) 45 + button = tk.Button( 46 + self, 47 + name=f"v{i+1}{j+1}", 48 + textvariable=button_value 49 + ) 50 + button.config(command=self._update_value_callback(i, j)) 51 + button.grid(row=i, column=j) 52 + self.grid_values[(i, j)] = button_value 53 + # Solve button 54 + self.solve_button = tk.Button( 55 + self, 56 + text="Solve", 57 + command=self.solve 58 + ) 59 + self.solve_button.grid(row=9, column=0, columnspan=5) 60 + # Status label 61 + self.status_label = tk.Label( 62 + self, 63 + text="unsolved" 64 + ) 65 + self.status_label.grid(row=9, column=4, columnspan=4) 66 + 67 + 68 + if __name__ == "__main__": 69 + app = Application() 70 + app.master.title("solpoku GUI") 71 + app.mainloop()
+6 -3
solpoku.py
··· 101 101 problem.set_objective(varnames) 102 102 return problem 103 103 104 + def set_objective_from_array(self, array: np.ndarray): 105 + self.set_objective( 106 + [f"v{i+1}{j+1}{array[i, j]}" for i, j in np.ndindex(array.shape) if array[i, j] != 0] 107 + ) 108 + 104 109 @classmethod 105 110 def from_array(cls, array: np.ndarray): 106 111 assert array.shape == (9, 9), "array is not 9x9" 107 112 problem = cls() 108 - problem.set_objective( 109 - [f"v{i+1}{j+1}{array[i, j]}" for i, j in np.ndindex(array.shape) if array[i, j] != 0] 110 - ) 113 + problem.set_objective_from_array(array) 111 114 return problem 112 115 113 116 def to_json(self, path: str, indent=True):