Flying memes

Creating Sudokus with Python

Following from my previous post I’ve explored with an algorithm to create Sudokus by iterating from an empty grid by adding valid numbers to random cells and then invoking the solving function twice, once navigating the solution space from 1 to 9 and the other in reverse. If both solving functions return the same, valid, grid then that’s the solution.


from pprint import pprint
from math import floor
from copy import deepcopy
from itertools import chain 
from random import shuffle

def solve(src_grid, range_dir):
  grid = deepcopy(src_grid)
  original_grid = deepcopy(src_grid)

  p = 0
  cycle = 0
  dir = 1

  while(p < 81 and p >= 0 and cycle < 1000000):
    cycle = cycle + 1
    x = floor(p / 9)
    y = p % 9
    if original_grid[x][y] != 0:
      p = p + dir
      continue
    candidates = list(filter(getFilter(range_dir, grid[x][y]), getCandidates(grid, x, y)))
    if range_dir == -1:
      candidates.reverse()
    prev_val = grid[x][y]
    if len(candidates) > 0:
      grid[x][y] = candidates[0]
    if grid[x][y] == prev_val:
      grid[x][y] = 0
      dir = -1
    else:
      dir = 1
    p = p + dir
  return grid

def getCandidates(grid, x, y):
  candidates = []
  col = [grid[r][y] for r in range(9)]
  sqr_x = floor(x / 3) * 3
  sqr_y = floor(y / 3) * 3
  sqr = [grid[sqr_x + floor(i/3)][sqr_y + (i % 3)] for i in range(9)]
  for t in range(1, 10):
    if t not in grid[x] and t not in col and t not in sqr:
      candidates.append(t)
  return candidates

def getFilter(range_dir, element):
  if range_dir == 1:
    return lambda n: n > element
  else:
    target = element if element > 0 else 10
    return lambda n: n < target

def fetchFromFile(path):
  grid = []
  content = None

  with open(path, 'r') as file:
      content = file.read().replace("\n", " ").split(" ")

  for row in range(9):
    grid.append([])
    for col in range(9):
      grid[row].append(int(content[row*9+col]))
  
  return grid

# to create
grid = []
for row in range(9):
  grid.append([])
  for col in range(9):
    grid[row].append(0)

cycle = 0
same = False
slots = [[x,y] for x in range(0,9) for y in range(0,9)]
shuffle(slots)
digits = 0

while (same is False and cycle < 100):
  cycle = cycle + 1
  x,y = slots.pop(1)
  candidaes = getCandidates(grid, x, y)
  shuffle(candidaes)
  grid[x][y] = candidaes[0]
  grid_f = list(chain.from_iterable(grid))
  solve_d = list(chain.from_iterable(solve(grid, 1)))
  solve_r = list(chain.from_iterable(solve(grid, -1)))
  same = solve_d == solve_r
  unsolvable = solve_d == grid_f or 0 in solve_d
  if unsolvable:
    same = False
    grid[x][y] = 0
    slots.append([x, y])
  else:
    digits = digits + 1

if same:
  pprint(grid)
else:
  pprint("No solutions found")

Running the above returns a valid Sudoku puzzle to be solved:


[[0, 6, 0, 0, 0, 8, 0, 3, 4],
 [8, 5, 0, 0, 0, 0, 7, 9, 0],
 [4, 7, 0, 0, 0, 0, 8, 1, 2],
 [0, 0, 0, 7, 0, 0, 4, 0, 0],
 [7, 0, 5, 0, 8, 0, 9, 0, 0],
 [0, 0, 0, 5, 1, 0, 2, 0, 0],
 [2, 0, 7, 0, 0, 0, 1, 0, 5],
 [0, 3, 0, 0, 2, 7, 0, 0, 9],
 [6, 0, 1, 0, 9, 0, 0, 2, 0]]