-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhst_block_block.go
181 lines (147 loc) · 5.29 KB
/
hst_block_block.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package sudoku
import (
"fmt"
"math/rand"
)
type blockBlockInteractionTechnique struct {
*basicSolveTechnique
}
func (self *blockBlockInteractionTechnique) humanLikelihood(step *SolveStep) float64 {
//TODO: think more about how difficult this technique is.
return self.difficultyHelper(60.0)
}
func (self *blockBlockInteractionTechnique) Candidates(grid Grid, maxResults int) []*SolveStep {
return self.candidatesHelper(self, grid, maxResults)
}
func (self *blockBlockInteractionTechnique) find(grid Grid, coordinator findCoordinator) {
pairs := pairwiseBlocks(grid)
//We're going to be looking at a lot of blocks again and again, so might as well cache this.
unfilledCellsForBlock := make([]CellSlice, DIM)
filledNumsForBlock := make([]IntSlice, DIM)
for i := 0; i < DIM; i++ {
unfilledCellsForBlock[i] = grid.Block(i).FilterByHasPossibilities()
filledNumsForBlock[i] = grid.Block(i).FilledNums()
}
//For each pair of blocks (in random order)
for _, pairIndex := range rand.Perm(len(pairs)) {
pair := pairs[pairIndex]
excludeNums := filledNumsForBlock[pair[0]].toIntSet().union(filledNumsForBlock[pair[1]].toIntSet())
for _, i := range rand.Perm(DIM) {
//See if we should stop doing work
if coordinator.shouldExitEarly() {
return
}
//Skip numbers entirely where either of the blocks has a cell with it set, since there obviously
//won't be any cells in both blocks that have that possibility.
if _, ok := excludeNums[i]; ok {
continue
}
//Find cells in each block that have that possibility.
firstBlockCells := unfilledCellsForBlock[pair[0]].FilterByPossible(i).CellReferenceSlice()
secondBlockCells := unfilledCellsForBlock[pair[1]].FilterByPossible(i).CellReferenceSlice()
//Now we need to figure out if these blocks are in the same row or same col
var majorAxisIsRow bool
rowOne, colOne, _, _ := blockExtents(pair[0])
rowTwo, colTwo, _, _ := blockExtents(pair[1])
if rowOne == rowTwo {
majorAxisIsRow = true
} else if colOne == colTwo {
majorAxisIsRow = false
} else {
panic("This shouldn't happen")
}
var blockOneIndexes IntSlice
var blockTwoIndexes IntSlice
if majorAxisIsRow {
blockOneIndexes = firstBlockCells.AllRows().Unique()
blockTwoIndexes = secondBlockCells.AllRows().Unique()
} else {
blockOneIndexes = firstBlockCells.AllCols().Unique()
blockTwoIndexes = secondBlockCells.AllCols().Unique()
}
if len(blockOneIndexes) != 2 || len(blockTwoIndexes) != 2 {
//There can only be two rows or columns in play for this technique to work.
continue
}
if !blockOneIndexes.SameContentAs(blockTwoIndexes) {
//Meh, they didn't line up into the same major axis cells.
continue
}
var targetCells CellRefSlice
if majorAxisIsRow {
targetCells = row(blockOneIndexes[0])
targetCells = append(targetCells, row(blockOneIndexes[1])...)
targetCells = targetCells.RemoveCells(block(pair[0])).RemoveCells(block(pair[1]))
} else {
targetCells = col(blockOneIndexes[0])
targetCells = append(targetCells, col(blockOneIndexes[1])...)
targetCells = targetCells.RemoveCells(block(pair[0])).RemoveCells(block(pair[1]))
}
//Okay, we have a possible set. Now we need to create a step.
step := &SolveStep{
self,
targetCells,
[]int{i},
append(firstBlockCells, secondBlockCells...),
nil,
nil,
}
if step.IsUseful(grid) {
if coordinator.foundResult(step) {
return
}
}
}
}
}
func (self *blockBlockInteractionTechnique) Description(step *SolveStep) string {
if len(step.TargetNums) != 1 {
return ""
}
blockNums := step.PointerCells.AllBlocks()
if len(blockNums) != 2 {
return ""
}
//make sure we get a stable order
blockNums.Sort()
var majorAxisIsRow bool
rowOne, colOne, _, _ := blockExtents(blockNums[0])
rowTwo, colTwo, _, _ := blockExtents(blockNums[1])
if rowOne == rowTwo {
majorAxisIsRow = true
} else if colOne == colTwo {
majorAxisIsRow = false
} else {
panic(1)
}
var groupName string
if majorAxisIsRow {
groupName = "rows"
} else {
groupName = "columns"
}
//TODO: explain this better. It's a confusing technique, and this description could be clearer.
return fmt.Sprintf("%d can only be in two different %s in blocks %s, which means that %d can't be in any other cells in those %s that aren't in blocks %s", step.TargetNums[0], groupName, blockNums.Description(), step.TargetNums[0], groupName, blockNums.Description())
}
//Technically in the future different grids could have different blcok partioning schemes
func pairwiseBlocks(grid Grid) [][]int {
//Returns a list of pairs of block IDs, where the blocks are in either the same row or column.
//TODO: implement this in a way that doesn't generate all of the pairs and cull.
var result [][]int
for _, pair := range subsetIndexes(DIM, 2) {
if len(pair) != 2 {
return nil
}
//Get the upper left cell cooordinates for both blocks.
rowOne, colOne, _, _ := blockExtents(pair[0])
rowTwo, colTwo, _, _ := blockExtents(pair[1])
//Then see if thw coords are the same.
if rowOne == rowTwo || colOne == colTwo {
//Found one that's the same, keep it
result = append(result, pair)
//Note: we don't have to worry about getting the same blocks back for the front and back of the pair, thanks
//to how subsetIndexes works.
}
}
return result
}