Add dlx solver (this is still a WIP)
The solver builds the constraint matrix. I have not fully verified its correctness, nor does the thing actually do the thing yet.
This commit is contained in:
parent
137e4e6436
commit
c9a86d0fdc
2 changed files with 138 additions and 1 deletions
|
@ -3,4 +3,4 @@
|
||||||
A library of Sudoku solvers.
|
A library of Sudoku solvers.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from . import backtracker
|
from . import backtracker, dlx
|
||||||
|
|
137
sudoku/solvers/dlx.py
Normal file
137
sudoku/solvers/dlx.py
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
# Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
from enum import Enum
|
||||||
|
from .. import SquareIsClue, ValueExistsInPeers, NoPossibleValues
|
||||||
|
|
||||||
|
class Backtrack(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConstraintKind(Enum):
|
||||||
|
CELL = 1
|
||||||
|
ROW = 2
|
||||||
|
COL = 3
|
||||||
|
BOX = 4
|
||||||
|
|
||||||
|
Constraint = namedtuple('CellConstraint', ['kind', 'index', 'value'])
|
||||||
|
Possibility= namedtuple('Possibility', ['row', 'col', 'value'])
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
'''
|
||||||
|
A doubly-linked list node that is a member of two distinct lists. One list is the row it is a member of. The other
|
||||||
|
list is the column it is a member of.
|
||||||
|
'''
|
||||||
|
def __init__(self):
|
||||||
|
# Horizontal linked list. West is "previous", east is "next".
|
||||||
|
self.west = self.east = self
|
||||||
|
# Vertical linked list. North is "previous", south is "next".
|
||||||
|
self.north = self.south = self
|
||||||
|
|
||||||
|
def insert_after_in_row(self, node):
|
||||||
|
self._insert(node, 'west', 'east')
|
||||||
|
|
||||||
|
def insert_before_in_col(self, node):
|
||||||
|
self._insert(node, 'south', 'north')
|
||||||
|
|
||||||
|
def iterate_row(self):
|
||||||
|
return self._iterate('east')
|
||||||
|
|
||||||
|
def iterate_col(self):
|
||||||
|
return self._iterate('south')
|
||||||
|
|
||||||
|
def _insert(self, node, prev_attr, next_attr):
|
||||||
|
self_east = getattr(self, next_attr) # Save my old next node
|
||||||
|
setattr(self, next_attr, node) # My next points to the new node
|
||||||
|
setattr(node, next_attr, self_east) # New node's next points to the old next
|
||||||
|
setattr(node, prev_attr, self) # New node's prev points to me
|
||||||
|
if self_east:
|
||||||
|
setattr(self_east, prev_attr, node) # Old next's prev points to the new node
|
||||||
|
|
||||||
|
def _iterate(self, next_attr):
|
||||||
|
cur = self
|
||||||
|
while cur:
|
||||||
|
yield cur
|
||||||
|
cur = getattr(cur, next_attr)
|
||||||
|
if cur == self:
|
||||||
|
break
|
||||||
|
|
||||||
|
class Header(Node):
|
||||||
|
'''
|
||||||
|
A column header, including a count of the number of rows in this column.
|
||||||
|
'''
|
||||||
|
def __init__(self, constraint):
|
||||||
|
Node.__init__(self)
|
||||||
|
self.constraint = constraint
|
||||||
|
self.number_of_rows = 0
|
||||||
|
|
||||||
|
def append(self, node):
|
||||||
|
self.insert_before_in_col(node)
|
||||||
|
self.number_of_rows += 1
|
||||||
|
node.header = self
|
||||||
|
|
||||||
|
class Cell(Node):
|
||||||
|
'''
|
||||||
|
A cell in the DLX matrix.
|
||||||
|
'''
|
||||||
|
def __init__(self, possibility):
|
||||||
|
super(Cell, self).__init__()
|
||||||
|
self.header = None
|
||||||
|
self.possibility = possibility
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '{}({})'.format(self.__class__.__name__, self.possibility)
|
||||||
|
|
||||||
|
def solve(sudoku):
|
||||||
|
'''
|
||||||
|
Implements DLX, Don Knuth's Dancing Links version of Algorithm X, for solving exact cover problems.
|
||||||
|
'''
|
||||||
|
# TODO: Construct the matrix based on the provided sudoku.
|
||||||
|
# TODO: Perform the algorithm on the matrix.
|
||||||
|
# TODO: With the solution from running the algorithm above, fill in the sudoku.
|
||||||
|
return sudoku
|
||||||
|
|
||||||
|
def _build_matrix(sudoku):
|
||||||
|
# 1. Create headers for all columns.
|
||||||
|
headers = _build_headers(sudoku)
|
||||||
|
_build_rows(sudoku, headers)
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def _build_headers(sudoku):
|
||||||
|
head = None
|
||||||
|
cur = None
|
||||||
|
|
||||||
|
def _insert_header(data):
|
||||||
|
header = Header(data)
|
||||||
|
if cur:
|
||||||
|
cur.insert_after_in_row(header)
|
||||||
|
return header
|
||||||
|
|
||||||
|
# Cell constraints
|
||||||
|
for i in range(sudoku.grid_size):
|
||||||
|
cur = _insert_header(Constraint(ConstraintKind.CELL, i, None))
|
||||||
|
|
||||||
|
# Row, Col, and Box constraints
|
||||||
|
for kind in (ConstraintKind.ROW, ConstraintKind.COL, ConstraintKind.BOX):
|
||||||
|
for i in range(sudoku.row_size):
|
||||||
|
for value in sudoku.possible_values:
|
||||||
|
cur = _insert_header(Constraint(kind, i, value))
|
||||||
|
|
||||||
|
# Head points to the first column header
|
||||||
|
head = cur.east
|
||||||
|
|
||||||
|
return head
|
||||||
|
|
||||||
|
def _build_rows(sudoku, headers):
|
||||||
|
for (index, coords) in enumerate(sudoku.all_squares):
|
||||||
|
board_value = sudoku.get(*coords)
|
||||||
|
possibilities = {board_value} if board_value else sudoku.possible_values_for_square(*coords)
|
||||||
|
for value in possibilities:
|
||||||
|
cur = None
|
||||||
|
for col in headers.iterate_row():
|
||||||
|
if col.constraint.index != index:
|
||||||
|
continue
|
||||||
|
cell = Cell(Possibility(*coords, value))
|
||||||
|
col.append(cell)
|
||||||
|
if cur:
|
||||||
|
cur.insert_after_in_row(cell)
|
||||||
|
cur = cell
|
Loading…
Add table
Add a link
Reference in a new issue