patternpythonMinor
Multithreaded Python implementation of a Sudoku validator
Viewed 0 times
validatorpythonimplementationsudokumultithreaded
Problem
I've just come across these day with the multithreading subject in Python. I remembered @200_success's answer and I thought of making that multithreaded as it follows:
I know multithreading won't bring any speed improvement in this case but I wanted to start learning about this subject and this looked like a good starting point.
```
from itertools import product
from threading import Thread
from timeit import default_timer as timer
import queue
DIGITS = set(range(1, 10))
def check_grid_size(grid):
"""Check that the grid is 9x9."""
well_formed = len(grid) == 9 and all(len(row) == 9 for row in grid)
return well_formed or None
def check_rows(grid, q):
"""Check that each number appears exactly once per row."""
q.put(all(set(row) == DIGITS for row in grid))
def check_columns(grid, q):
"""Check that each number appears exactly once per column."""
columns = [[row[c] for row in grid] for c in range(9)]
q.put(all(set(col) == DIGITS for col in columns))
def check_3x3_grid(grid, q):
"""Check that each number appears exactly once per 3x3 grid."""
threes = [(0, 1, 2), (3, 4, 5), (6, 7, 8)]
for row_block, col_block in product(threes, threes):
block = [grid[r][c] for r, c in product(row_block, col_block)]
if set(block) != DIGITS:
q.put(False)
return
q.put(True)
def check_sudoku(grid):
"""
Validate a sudoku solution.
Given a grid as a list of lists, return None if it is ill-formed,
False if it is invalid, or True if it is a valid solution.
"""
assert isinstance(grid, list)
q = queue.Queue()
if not check_grid_size(grid):
return None
row_thread = Thread(target=check_rows, args=(grid, q))
row_thread.start()
columns_thread
- one thread to check that each column contains the digits 1-9 only once
- one thread to check that each row contains the digits 1-9 only once
- another nine threads to check each 3x3 sub-grid for the digits 1-9
I know multithreading won't bring any speed improvement in this case but I wanted to start learning about this subject and this looked like a good starting point.
```
from itertools import product
from threading import Thread
from timeit import default_timer as timer
import queue
DIGITS = set(range(1, 10))
def check_grid_size(grid):
"""Check that the grid is 9x9."""
well_formed = len(grid) == 9 and all(len(row) == 9 for row in grid)
return well_formed or None
def check_rows(grid, q):
"""Check that each number appears exactly once per row."""
q.put(all(set(row) == DIGITS for row in grid))
def check_columns(grid, q):
"""Check that each number appears exactly once per column."""
columns = [[row[c] for row in grid] for c in range(9)]
q.put(all(set(col) == DIGITS for col in columns))
def check_3x3_grid(grid, q):
"""Check that each number appears exactly once per 3x3 grid."""
threes = [(0, 1, 2), (3, 4, 5), (6, 7, 8)]
for row_block, col_block in product(threes, threes):
block = [grid[r][c] for r, c in product(row_block, col_block)]
if set(block) != DIGITS:
q.put(False)
return
q.put(True)
def check_sudoku(grid):
"""
Validate a sudoku solution.
Given a grid as a list of lists, return None if it is ill-formed,
False if it is invalid, or True if it is a valid solution.
"""
assert isinstance(grid, list)
q = queue.Queue()
if not check_grid_size(grid):
return None
row_thread = Thread(target=check_rows, args=(grid, q))
row_thread.start()
columns_thread
Solution
The way you're starting the block threads looks wrong to me. You're starting each thread with the same parameters (the grid and a queue):
I can't see anywhere that the thread is told which block it is supposed to check. So, instead of starting 9 threads, one to check each block, it looks like you're starting 9 threads and having each of them check all nine of the blocks.
You could partition the blocks and then start a thread to validate each block, but this seems like overkill. Simply starting a single thread to check the nine blocks is the smallest change and probably the right one to make, since checking nine blocks is almost the same amount of work as checking the nine horizontals/verticals. With that in mind, you can simply change the code to the following (an achieve the same result with 8 less threads):
Or a slightly more concise:
t = Thread(target=check_3x3_grid, args=(grid, q))I can't see anywhere that the thread is told which block it is supposed to check. So, instead of starting 9 threads, one to check each block, it looks like you're starting 9 threads and having each of them check all nine of the blocks.
You could partition the blocks and then start a thread to validate each block, but this seems like overkill. Simply starting a single thread to check the nine blocks is the smallest change and probably the right one to make, since checking nine blocks is almost the same amount of work as checking the nine horizontals/verticals. With that in mind, you can simply change the code to the following (an achieve the same result with 8 less threads):
#...
grid_thread = Thread(target=check_3x3_grid, args=(grid, q))
grid_thread.start()
#...
grid_thread.join()Or a slightly more concise:
validation_threads = [Thread(target=check_rows, args=(grid, q))
,Thread(target=check_columns, args=(grid, q))
,Thread(target=check_3x3_grid, args=(grid, q))]
[t.start() for t in validation_threads]
[t.join() for t in validation_threads]Code Snippets
t = Thread(target=check_3x3_grid, args=(grid, q))#...
grid_thread = Thread(target=check_3x3_grid, args=(grid, q))
grid_thread.start()
#...
grid_thread.join()validation_threads = [Thread(target=check_rows, args=(grid, q))
,Thread(target=check_columns, args=(grid, q))
,Thread(target=check_3x3_grid, args=(grid, q))]
[t.start() for t in validation_threads]
[t.join() for t in validation_threads]Context
StackExchange Code Review Q#143188, answer score: 3
Revisions (0)
No revisions yet.