The Challenge
Since I came across the first sudoku I was wondering if it could by solved by SQL using queries that select just the right value for a cell.Well, some day I tried, here is what I have got now.
Domain Tables
I decided to set up a few tables to represent information about the game without clearly knowing in advance how I am going to use them in queries. But I was quite sure that I was going to need this data somehow.
Values
The period I intended to use for representing an empty cell. The column Mask was introduced later, it turned out to be helpful.CREATE TABLE Token(Character CHAR(1) NOT NULL,Value SMALLINT NULL,Mask INT NULL,CONSTRAINT PK_Token PRIMARY KEY CLUSTERED (Character));INSERT INTO Token(Character, Value, Mask)VALUES('.', NULL, NULL),('1', 1, 1),('2', 2, 2),('3', 3, 4),('4', 4, 8),('5', 5, 16),('6', 6, 32),('7', 7, 64),('8', 8, 128),('9', 9, 256)GOCREATE VIEW Value ASSELECT Value, MaskFROM TokenWHERE Value IS NOT NULL;
Location
I was going to need information about the location of cells, for grouping to rows, colums and squares. Because I was not sure if it is better to model the location "deep" or "wide", I made both. This data is static, so there is no problem with duplication.CREATE TABLE Location(CellID SMALLINT NOT NULL,GroupType CHAR(1) NOT NULL, -- 'C', 'R', 'S' meaning column, row and squareGroupValue SMALLINT NOT NULL,CONSTRAINT PK_Location PRIMARY KEY CLUSTERED (CellID,GroupType));INSERT INTO Location (CellID, GroupType, GroupValue)SELECT r.Value*10 + c.Value AS CellID, gt.GroupType, CASE WHEN gt.GroupType='C' THEN c.Value WHEN gt.GroupType='R' THEN r.Value ELSE (c.Value-1)/3 + ((r.Value-1)/3)*3 + 1 END AS GroupValueFROM (VALUES ('C'), ('R'), ('S') ) gt(GroupType)cross join Value rcross join Value cCREATE TABLE LocationWide(CellID SMALLINT NOT NULL,[Column] SMALLINT NOT NULL,[Row] SMALLINT NOT NULL,[Square] SMALLINT NOT NULL,CONSTRAINT PK_LocationWide PRIMARY KEY CLUSTERED (CellID));INSERT INTO LocationWide (CellID, [Column], [Row], [Square])SELECT p.CellID, p.C AS [Column], p.R AS [Row], p.S AS [Square]FROM (SELECT CellID, GroupType, GroupValue FROM Location) lPIVOT (MAX(GroupValue) FOR GroupType IN ([C], [R], [S])) p;
First 20 rows of LocationWide:
CellID
|
Column
|
Row
|
Square
|
11
|
1
|
1
|
1
|
12
|
2
|
1
|
1
|
13
|
3
|
1
|
1
|
14
|
4
|
1
|
2
|
15
|
5
|
1
|
2
|
16
|
6
|
1
|
2
|
17
|
7
|
1
|
3
|
18
|
8
|
1
|
3
|
19
|
9
|
1
|
3
|
21
|
1
|
2
|
1
|
22
|
2
|
2
|
1
|
23
|
3
|
2
|
1
|
24
|
4
|
2
|
2
|
25
|
5
|
2
|
2
|
26
|
6
|
2
|
2
|
27
|
7
|
2
|
3
|
28
|
8
|
2
|
3
|
29
|
9
|
2
|
3
|
31
|
1
|
3
|
1
|
32
|
2
|
3
|
1
|
Cell
The columns Step and Rule are for traceability, I want to know why and when a cell was fillled.CREATE TABLE Cell(CellID SMALLINT NOT NULL,Value SMALLINT NOT NULL,Step INT IDENTITY(1,1) NOT NULL,[Rule] nvarchar(100) NULL,CONSTRAINT PK_Cell PRIMARY KEY CLUSTERED (CellID));
Candidate
The table Candidate holds the values for a cell that are considered possible at the moment. The idea is to remove rows from the table candidate by rules. When there is only one candidate left its value is assigned to the cell, which removes further candidates for other cells.CREATE TABLE Candidate(CellID SMALLINT NOT NULL,Value SMALLINT NOT NULL,Mask INT NOT NULL,CONSTRAINT PK_Candidate PRIMARY KEY CLUSTERED (CellID,Value));CREATE TABLE CandidateMask (CellID SMALLINT NOT NULL,Mask INT NOT NULL,CONSTRAINT PK_CandidateMask PRIMARY KEY (CellID));
The table CandidateMask was introduced later as ist turned out to be convenient to operate not only on certain values, but on sets of values. That leads us to sets.
Sets
The SetN tables hold the combinations of N values. Here are listed only Set1 and Set2, the download links in part 3 give you the complete source. What we have now is, showing the first 20 rows,:CREATE TABLE Set1(SetValue SMALLINT NOT NULL,Value1 SMALLINT NOT NULL,DisplayValue VARCHAR(10) NOT NULL,CONSTRAINT PK_Set1 PRIMARY KEY CLUSTERED (SetValue));INSERT INTO Set1 (SetValue, Value1, DisplayValue)SELECT v1.Mask, v1.Value, STUFF('.........', v1.Value, 1, CAST(v1.Value AS CHAR(1)))FROM Value v1;GOCREATE TABLE Set2(SetValue SMALLINT NOT NULL,Value1 SMALLINT NOT NULL,Value2 SMALLINT NOT NULL,DisplayValue VARCHAR(10) NOT NULL,CONSTRAINT PK_Set2 PRIMARY KEY CLUSTERED (SetValue));INSERT INTO Set2 (SetValue, Value1, Value2, DisplayValue)SELECT s.SetValue+v.Mask, s.Value1, v.Value, STUFF(s.DisplayValue, v.Value, 1, CAST(v.Value AS CHAR(1)))FROM Set1 sINNER JOIN Value v ON v.Value>s.Value1;
SELECT * FROM SetAll ORDER BY Size, SetValue
SetValue
|
Size
|
DisplayValue
|
1
|
1
|
1........
|
2
|
1
|
.2.......
|
4
|
1
|
..3......
|
8
|
1
|
...4.....
|
16
|
1
|
....5....
|
32
|
1
|
.....6...
|
64
|
1
|
......7..
|
128
|
1
|
.......8.
|
256
|
1
|
........9
|
3
|
2
|
12.......
|
5
|
2
|
1.3......
|
6
|
2
|
.23......
|
9
|
2
|
1..4.....
|
10
|
2
|
.2.4.....
|
12
|
2
|
..34.....
|
17
|
2
|
1...5....
|
18
|
2
|
.2..5....
|
20
|
2
|
..3.5....
|
Domain Logic
Validity
If we get 0 rows from this view, the cell values are valid.CREATE VIEW Violation ASSELECT l.GroupType, l.GroupValue, c.Value, COUNT(*) AS MultiplicityFROM Cell cINNER JOIN Location l ON c.CellID = l.CellIDGROUP BY l.GroupType, l.GroupValue, c.ValueHAVING COUNT(*) > 1;
Consistency
If rows are added to table Cell, candidates have to be removed. When removing candidates, table CandidateMask has to be updated.CREATE TRIGGER TrimCandidates ON Cell AFTER INSERT ASBEGINDELETE FROM Candidate WHERE CellID IN (SELECT CellID FROM inserted)DELETE FROM cFROM inserted iINNER JOIN Location l1 ON l1.CellID=i.CellIDINNER JOIN Location l2 ON l1.GroupType=l2.GroupType AND l1.GroupValue=l2.GroupValueINNER JOIN Candidate c ON l2.CellID=c.CellID AND c.Value=i.ValueEND;GOCREATE TRIGGER TrimCandidateMask ON Candidate AFTER DELETE ASBEGINUPDATE cmSET cm.Mask=cm.Mask & (~d.Mask)FROM CandidateMask cmINNER JOIN (SELECT d.CellID, SUM(d.Mask) AS MaskFROM deleted dGROUP BY d.CellID) d ON d.CellID=cm.CellIDDELETE FROM CandidateMaskWHERE Mask=0END;
Initialisation
As a first step, all possible candidates are inserted. It is assumed that there are some rows in table Cell that represent the "start position".CREATE PROCEDURE FillCandidates ASBEGININSERT INTO Candidate (CellID, Value, Mask)SELECT l.CellID, v.Value, v.MaskFROM Location lCROSS JOIN Value vWHERE l.GroupType='C'AND NOT EXISTS (SELECT 2 FROM Cell c WHERE c.CellID=l.CellID) -- any CellID that does not have a value yetAND NOT EXISTS ( -- any value that is not present in same groupSELECT 2 FROM Location lcINNER JOIN Cell cINNER JOIN Location l2 ON c.CellID=l2.CellIDON lc.GroupType=l2.GroupType AND lc.GroupValue=l2.GroupValueWHERE lc.CellID=l.CellID AND c.Value=v.Value)INSERT INTO CandidateMask (CellID, Mask)SELECT c.CellID, SUM(c.Mask) AS MaskFROM Candidate cGROUP BY c.CellIDEND;
What we have right now
Domain Tables |
Next Part
In the next part I will introduce the queries and logic for solving.
No comments:
Post a Comment