4

So I am trying to solve a problem which makes you compare numbers in the 9x9 sudoku grid so see if the game of Sudoku has a valid/solvable grid of numbers (Meaning that the rules of Sudoku apply to the given grid). I have pretty much solved the problem, but I am stuck on this last part. I have figured out how sum up each element on each column/row, but I am not able to completely solve this problem until I can actually check each 3x3 grid to see if there are no duplicate numbers in them. This is where I am stuck because I cannot seem to get the right algorithm to iterate through the matrix in a 3x3 fashion.

I tried to control the iteration completely by using a series of for loops that increase the certain index number so it moves along the matrix. Let me know what I am doing wrong--or if there are any other possible, more elegant ways to solve this problem (using so many for loops makes my code look long and ugly/less efficient).

def sudoku(grid):

grid = [[1,3,2,5,4,6,9,8,7], 
        [4,6,5,8,7,9,3,2,1], 
        [7,9,8,2,1,3,6,5,4], 
        [9,2,1,4,3,5,8,7,6], 
        [3,5,4,7,6,8,2,1,9], 
        [6,8,7,1,9,2,5,4,3], 
        [5,7,6,9,8,1,4,3,2], 
        [2,4,3,6,5,7,1,9,8], 
        [8,1,9,3,2,4,7,6,5]]
duplicate = set()
numHolder = 0
for a in range(0,9):
    for b in range(0,9):
        numHolder+=grid[b][a]
    if numHolder!=45:
        return False
    numHolder=0
for b in range(0,9):
    for x in range(0, 9):
        numHolder += grid[b][x]
    if numHolder != 45:
        return False
    numHolder = 0

for b in range(0,3):
    for c in range(0,3):
        if grid[b][c] in duplicate:
            return False
        else:
            duplicate.add(grid[b][c])
duplicate.clear()
for d in range(0,3):
    for e in range(0,3):
        if grid[d][c+3] in duplicate:
            return False
        else:
            duplicate.add(grid[d][c+3])

duplicate.clear()
for d in range(0,3):
    for e in range(0,3):
        if grid[b][c+6] in duplicate:
            return False
        else:
            duplicate.add(grid[d][c+6])
duplicate.clear()
for d in range(0,3):
    for e in range(0,3):
        if grid[d+3][c] in duplicate:
            return False
        else:
            duplicate.add(grid[d+3][c])

duplicate.clear()
for d in range(0,3):
    for e in range(0,3):
        if grid[d+3][c+3] in duplicate:
            return False
        else:
            duplicate.add(grid[d+3][c+3])
duplicate.clear()
for d in range(0,3):
    for e in range(0,3):
        if grid[d+3][c+6] in duplicate:
            return False
        else:
            duplicate.add(grid[d+3][c+6])
duplicate.clear()
for d in range(0,3):
    for e in range(0,3):
        if grid[d+6][c] in duplicate:
            return False
        else:
            duplicate.add(grid[d+6][c])

duplicate.clear()
for d in range(0,3):
    for e in range(0,3):
        if grid[d+6][c+3] in duplicate:
            return False
        else:
            duplicate.add(grid[d+6][c+3])

duplicate.clear()
for d in range(0,3):
    for e in range(0,3):
        if grid[d+6][c+6] in duplicate:
            return False
        else:
            duplicate.add(grid[d+6][c+6])
return True

5 Answers5

2

This can be solved quite easily in NumPy:

import numpy as np
import skimage

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

# Create NumPy array
grid = np.array(grid)
# Cut into 3x3 sublocks, and flatten each subblock
subgrids = skimage.util.view_as_blocks(grid, (3, 3)).reshape(3, 3, -1)

# Sort each subblock then compare each one with [1, 2, ..., 9]. If all are equal, it is a valid subblock
valid_blocks = np.all(np.sort(subgrids, axis=-1) == np.arange(1, 10), axis=-1)
# array([[ True,  True,  True],
#        [ True,  True,  True],
#        [ True,  True,  True]])

# Sort rows, then compare each one
valid_rows = np.all(np.sort(grid, axis=1) == np.arange(1, 10), axis=1)
# array([ True,  True,  True,  True,  True,  True,  True,  True,  True])

# Sort columns, then compare each one
valid_columns = np.all(np.sort(grid, axis=0) == np.arange(1, 10)[:, None], axis=0)
# array([ True,  True,  True,  True,  True,  True,  True,  True,  True])

# Check if all comparisons were all true
all_valid = np.all(valid_blocks) & np.all(valid_rows) & np.all(valid_columns)
# True
Nils Werner
  • 34,832
  • 7
  • 76
  • 98
1

You can get a list of 3x3 from your 9x9 with

mats_3x3x9 = [grid[3*i:3*i+3] for i in range(3)]
mats_9x3x3 = [
    [row_1x9[3*i:3*i+3] for row_1x9 in rows_3x9]
    for i in range(3)
    for rows_3x9 in mats_3x3x9
]

You can then check each 3x3 contains the number 1 to 9 with e.g.

for mat_3x3 in mats_9x3x3:
    if not sorted([i for row in mat_3x3 for i in row]) == list(range(1,10)):
        return False
return True

Though you could probably get the smaller matrices easier with numpy

import numpy as np
mats_9x3x3 = np.array(grid)
[mats_9x3x3[3*i:3*i+3, 3*j:3*j+3] for i in range(3) for j in range(3)]

You could then find duplicate values as in this question, with e.g.

from collections import Counter
for mat_3x3 in mats_9x3x3:
    if [item for item, count in Counter(mat_3x3).iteritems() if count > 1]:
        return False
return True
joel
  • 6,359
  • 2
  • 30
  • 55
0

If you wish to check all constraints of a sudoku in plain python, it is best to generate all constraints as lists of values that can be summed up:

def check_constraints(constraints, target_sum):
    for _constraint in constraints:
        if sum(_constraint) != target_sum:
            return False
    return True

To extract sub-matrices of a grid organized as list of rows, a nested iteration over a set of horizontal ranges and a set of vertical ranges is sufficient:

def get_grid_constraints(grid, h_ranges, v_ranges):
    constraints = []
    for h_start, h_end in h_ranges:
        for v_start, v_end in v_ranges:
            _constraint = []
            constraints.append(_constraint)
            for _line in grid[v_start:v_end]:
                _constraint.extend(_line[h_start:h_end])
    return constraints

The two sets of ranges are generated with function make_ranges:

def make_ranges(h_step, v_step, length):
    return (make_range(h_step, length), make_range(v_step, length))

which calls make_range for each direction:

def make_range(step, width):
    range_ = []
    _start = 0
    for _end in range(step, width+1, step):
        range_.append((_start, _end))
        _start = _end
    return range_

To keep the algorithm flexible, the grid parameters are defined as variables:

SUDOKU_WIDTH = 3
SUDOKU_HEIGHT = 3
CONSTRAINT_LEN = SUDOKU_WIDTH * SUDOKU_HEIGHT
CONSTRAINT_SUM = sum(range(CONSTRAINT_LEN + 1))

The generic range sets are created as:

row_ranges = make_ranges(CONSTRAINT_LEN, 1, CONSTRAINT_LEN)
col_ranges = make_ranges(1, CONSTRAINT_LEN, CONSTRAINT_LEN)
quad_ranges = make_ranges(SUDOKU_WIDTH, SUDOKU_HEIGHT, CONSTRAINT_LEN)

The respective constraints are extracted from the grid:

rows = get_grid_constraints(GRID, *row_ranges)
cols = get_grid_constraints(GRID, *col_ranges)
quads = get_grid_constraints(GRID, *quad_ranges)

for checking against the CONSTRAINT_SUM:

is_valid = check_constraints(rows + cols + quads, CONSTRAINT_SUM)

This allows for detailed debug output to verify proper operation of the algorithms:

dbg_fwid = 11
dbg_sep = '\n                 {1:<{0}s}'.format(dbg_fwid, '')
def dbg_lists(lists):
    return dbg_sep.join((str(_l) for _l in lists))
def sformat(fmt, *args, **kwargs):
    return fmt.format(*args, **kwargs)

print(sformat("#    "":DBG:    {1:<{0}s}: ]{2!s}[", dbg_fwid, "CONSTRAINT_SUM", (CONSTRAINT_SUM)))
print(sformat("#    "":DBG:    {1:<{0}s}: ]{2!s}[", dbg_fwid, "row_ranges", (row_ranges)))
print(sformat("#    "":DBG:    {1:<{0}s}: ]{2!s}[", dbg_fwid, "col_ranges", (col_ranges)))
print(sformat("#    "":DBG:    {1:<{0}s}: ]{2!s}[", dbg_fwid, "quad_ranges", (quad_ranges)))
print(sformat("#    "":DBG:    {1:<{0}s}: ]{2!s}[", dbg_fwid, "rows", dbg_lists(rows)))
print(sformat("#    "":DBG:    {1:<{0}s}: ]{2!s}[", dbg_fwid, "cols", dbg_lists(cols)))
print(sformat("#    "":DBG:    {1:<{0}s}: ]{2!s}[", dbg_fwid, "quads", dbg_lists(quads)))
print(sformat("#    "":DBG:    {1:<{0}s}: ]{2!s}[", dbg_fwid, "is_valid", (is_valid)))

which shows:

CONSTRAINT_SUM: ]45[
row_ranges : ]([(0, 9)], [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)])[
col_ranges : ]([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9)], [(0, 9)])[
quad_ranges: ]([(0, 3), (3, 6), (6, 9)], [(0, 3), (3, 6), (6, 9)])[
rows       : ][1, 3, 2, 5, 4, 6, 9, 8, 7]
              [4, 6, 5, 8, 7, 9, 3, 2, 1]
              [7, 9, 8, 2, 1, 3, 6, 5, 4]
              [9, 2, 1, 4, 3, 5, 8, 7, 6]
              [3, 5, 4, 7, 6, 8, 2, 1, 9]
              [6, 8, 7, 1, 9, 2, 5, 4, 3]
              [5, 7, 6, 9, 8, 1, 4, 3, 2]
              [2, 4, 3, 6, 5, 7, 1, 9, 8]
              [8, 1, 9, 3, 2, 4, 7, 6, 5][
cols       : ][1, 4, 7, 9, 3, 6, 5, 2, 8]
              [3, 6, 9, 2, 5, 8, 7, 4, 1]
              [2, 5, 8, 1, 4, 7, 6, 3, 9]
              [5, 8, 2, 4, 7, 1, 9, 6, 3]
              [4, 7, 1, 3, 6, 9, 8, 5, 2]
              [6, 9, 3, 5, 8, 2, 1, 7, 4]
              [9, 3, 6, 8, 2, 5, 4, 1, 7]
              [8, 2, 5, 7, 1, 4, 3, 9, 6]
              [7, 1, 4, 6, 9, 3, 2, 8, 5][
quads      : ][1, 3, 2, 4, 6, 5, 7, 9, 8]
              [9, 2, 1, 3, 5, 4, 6, 8, 7]
              [5, 7, 6, 2, 4, 3, 8, 1, 9]
              [5, 4, 6, 8, 7, 9, 2, 1, 3]
              [4, 3, 5, 7, 6, 8, 1, 9, 2]
              [9, 8, 1, 6, 5, 7, 3, 2, 4]
              [9, 8, 7, 3, 2, 1, 6, 5, 4]
              [8, 7, 6, 2, 1, 9, 5, 4, 3]
              [4, 3, 2, 1, 9, 8, 7, 6, 5][
is_valid   : ]True[
wolfmanx
  • 481
  • 5
  • 12
0

A simple solution using 2 steps can be the following:

1)

# extract tuples of 3, row-wise
x = 0
listTuple=[None]*9*3
for i in range(9):
    for j in range(3):
        listTuple[x] = grid[i][j*3:j*3+3]
        x += 1

2)

# Take the 3 tuples which form a box and compare with 1-9 numbers
rangeList = list(range(1,10))
idx = 0
for i in range(1,10):
    box = sorted(listTuple[idx]+listTuple[idx+3]+listTuple[idx+6]) 
    print(box == rangeList)
    if i%3 == 0:
        idx = idx + 6
    idx += 1
0

Here is solution without any in-built python function and can be converted in any programming language if you know how to do so, The limitation of this code is that it works only for 9 X 9 Matrix. You will have to change the value of i,j and cntr if you want to increase the matrix size.

def sudoku(grid):
    i=0
    j=0
    cntr=0
    while(cntr<9):
        duplicate=set()
        for x in range(i,i+3):
            for y in range(j,j+3):
                if grid[b][c] in duplicate:
                    return False
                else:
                    duplicate.add(grid[b][c])
   
        i+=3
        cntr+=1
        if(cntr==3):
            i=0
            j=3
        if(cntr==6):
            i=0
            j=6
    return True

If you wish to just get the array in 3X3 from then you can just initialize an array in place of set and remove those checking duplicate conditions. and append that array in a main array.