-
Notifications
You must be signed in to change notification settings - Fork 0
/
dlx_contexpr.h
382 lines (333 loc) · 15.5 KB
/
dlx_contexpr.h
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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
/**
* dlx.cpp
*
* By Sebastian Raaphorst, 2018.
*/
#pragma once
#include <array>
#include <cassert>
#include <optional>
#include <tuple>
namespace dlx {
/** INPUT PARAMETERS **/
using index = std::size_t;
using position = std::pair<int, int>;
template<size_t NumNodes>
using position_array = std::array<position, NumNodes>;
/**
* We represent an instance of the dancing links algorithm (an exact cover problem) which comprises:
* 1. A header, representing the elements of the set to cover.
* 2. A collection of rows, representing the subsets, with entries in the columns for the elements they cover.
* We represent the set of things to cover as the set {0, 1, ... N-1} for a given N.
* It is the responsibility of the user to map these values to meaningful data with respect to their problem.
*
* Note that the algorithm can be run entirely at compile time (but need not be),and only the first solution
* found is returned. It would be ideal if we could return all solutions found, but I cannot ascertain a way to
* do so in a constexpr setting. (We could, for example, allocate enough room for, say, 100 solutions and see
* how many the algorithm finds.)
*
* Note that DLX is uninstantiable and thus just a class of static methods. They could be separated, but we keep
* them so categorized so that the template sizes carry around, and also to enforce encapsulation, as there
* are certain methods the user should not call.
*
* There are a number of improvements that could be made: these will be documented as issues in github.
*
* @tparam NumCols the number of elements in the set to cover
* @tparam NumRows the number of rows n the array of nodes
* @tparam NumNodes the size of the array of nodes
*/
template<size_t NumCols, size_t NumRows, size_t NumNodes>
class DLX final {
public:
/** OUTPUT **/
using solution = std::array<bool, NumRows>;
private:
/** INTERNAL DATA TYPES **/
static constexpr size_t dim = NumCols + 1 + NumNodes;
using direction = std::array<index, dim>;
// The index of the header, the size of the header, the length of a column, and the type of column, which
// determines if columns are primary (must be covered once) or secondary (covered at most once).
static constexpr index header = NumCols;
static constexpr size_t HeaderSize = NumCols + 1;
using column_length = std::array<size_t, HeaderSize>;
// A mapping from nodes to rows.
// The header counts as the NumRows-th row.
using row_mapping = std::array<index, dim>;
/**
* The internal state used to model the problem.
*/
struct data {
// Header-specific: column type and length.
column_length S{};
// For all nodes: directional indices, including the column header.
direction R{}; // right
direction L{}; // left
direction U{}; // up
direction D{}; // down
direction C{}; // column header
// The coordinates of rows.
row_mapping RM{};
};
/**
* Covers a column, i.e. removes the column from the header, and then removes all rows that have entries
* in the column from the problem.
*
* Note that executing:
* 1. coverColumn(state, idx)
* 2. uncoverColumn(state, idx)
* should leave the problem unchanged.
*
* @param state the DLX state
* @param columnIdx the index of the column
*/
template <typename Data>
static constexpr void coverColumn(Data &&state, index columnIdx) noexcept {
assert(0 <= columnIdx && columnIdx < header);
// Remove the column from the header.
state.L[state.R[columnIdx]] = state.L[columnIdx];
state.R[state.L[columnIdx]] = state.R[columnIdx];
// Iterate over the rows covered by this column and remove them.
for (index i = state.D[columnIdx]; i != columnIdx; i = state.D[i]) {
// Remove row i from the problem.
for (index j = state.R[i]; j != i; j = state.R[j]) {
state.U[state.D[j]] = state.U[j];
state.D[state.U[j]] = state.D[j];
--state.S[state.C[j]];
}
}
}
/**
* Uncovers a column, i.e. restores all rows that have entries in the column, and then restores the column
* to the header.
*
* Note that executing:
* 1. coverColumn(state, idx)
* 2. uncoverColumn(state, idx)
* should leave the problem unchanged.
*
* @param state the DLX state
* @param columnIdx the index of the column
*/
template <typename Data>
static constexpr void uncoverColumn(Data &&state, index columnIdx) noexcept {
assert(0 <= columnIdx && columnIdx < header);
// Reverse the removal of the rows from coverColumn.
for (index i = state.U[columnIdx]; i != columnIdx; i = state.U[i]) {
// Restore row i to the problem.
for (index j = state.L[i]; j != i; j = state.L[j]) {
++state.S[state.C[j]];
state.D[state.U[j]] = j;
state.U[state.D[j]] = j;
}
}
// Restore the column to the header.
state.R[state.L[columnIdx]] = columnIdx;
state.L[state.R[columnIdx]] = columnIdx;
}
/**
* Given a node index for a node in a row, include the row in the partial solution.
*
* Note that executing:
* 1. useRow(state, idx, sol)
* 2. unuseRow(state, idx, sol)
* should leave the problem unchhanged.
*
* This method could be used to force certain rows into the solution.
* This would be a simple modification to make.
*
* @param d the DLX state
* @param rowIdx the index of a node in the row
* @param sol the solution
*/
template <typename Data, typename Solution>
static constexpr void useRow(Data &&state, index rowIdx, Solution &&sol) noexcept {
assert(rowIdx > header && rowIdx < HeaderSize + NumNodes);
sol[state.RM[rowIdx]] = true;
// Cover all the columns in the row.
index i = rowIdx;
do {
coverColumn(std::forward<Data>(state), state.C[i]);
i = state.R[i];
//} while (i != initialIdx);
} while (i != rowIdx);
}
/**
* Given a node index for a node in a row, remove the row from the partial solution.
*
* Note that executing:
* 1. useRow(state, idx, sol)
* 2. unuseRow(state, idx, sol)
* should leave the problem unchhanged.
*
* @param d the DLX state
* @param rowIdx the index of a node in the row
* @param sol the solution
*/
template <typename Data, typename Solution>
static constexpr void unuseRow(Data &&state, index rowIdx, Solution &&sol) noexcept {
assert(rowIdx > header && rowIdx < HeaderSize + NumNodes);
sol[state.RM[rowIdx]] = false;
// Uncover all the columns in the row.
index i = rowIdx;
do {
uncoverColumn(std::forward<Data>(state), state.C[i]);
i = state.L[i];
} while (i != rowIdx);
}
/**
* Given a formulation produced by the run method, attempt to locate a solution to the exact cover
* problem using backtracking on the rows. The general strategy is:
* 1. If all columns are covered, solution found.
* 2. Otherwise pick the column with the least rows (to minimize branching factor).
* 3. Recurse over all possibilities of adding the rows covering thhe column to the answer.
*
* The algorithm manipulates the links between rows and columns as per Knuth's DLX paper to efficiently
* expand / backtrack.
*
* @param state the DLX state
* @param sol an array representing the solution
* @return a solution if one exists, and nullopt if not
*/
template <typename Data, typename Solution>
static constexpr std::optional<solution> find_solution(Data &&state, Solution &&sol) noexcept {
// Check to see if we have a complete solution, i.e if the header only loops to itself.
// We could modify this to make a callback for solutions and then iterate over them, but
// I'm not sure how we would do this with constexpr.
if (state.R[header] == header)
return sol;
// Choose the column with the smallest number of rows to minimize the branching factor.
index minColumnIndex = state.R[header];
for (index i = state.R[minColumnIndex]; i != header; i = state.R[i])
if (state.S[i] < state.S[minColumnIndex])
minColumnIndex = i;
// If there ae no available rows to cover this column, we cannot extend.
if (minColumnIndex == header || state.S[minColumnIndex] == 0)
return std::nullopt;
// Cover the column.
coverColumn(std::forward<Data>(state), minColumnIndex);
// Now extend the solution by trying each possible row in the column.
for (index i = state.D[minColumnIndex]; i != minColumnIndex; i = state.D[i]) {
// I feel like we should be able to use useRow / unuseRow in this for loop, but it doesn't seem to
// produce the correct answers in either this or my Python implementation of DLX.
// useRow(std::forward<Data>(state), i, std::forward<Solution>(sol));
sol[state.RM[i]] = true;
for (index j = state.R[i]; j != i; j = state.R[j])
coverColumn(std::forward<Data>(state), state.C[j]);
//
// Recurse and see if we can find a solution.
const auto soln = find_solution(std::forward<Data>(state), std::forward<Solution>(sol));
if (soln.has_value())
return soln;
// Reverse the operation.
// unuseRow(std::forward<Data>(state), i, std::forward<Solution>(sol));
sol[state.RM[i]] = false;
for (index j = state.L[i]; j != i; j = state.L[j])
uncoverColumn(std::forward<Data>(state), state.C[j]);
//
}
// Uncover the column.
uncoverColumn(std::forward<Data>(state), minColumnIndex);
// If we reach this point, we could not find a row that leads to completion.
return std::nullopt;
}
/**
* Initialize the problem by taking the array of positions and populating the DLX array.
* These define the rows that comprise the subets for the exact cover.
*
* @param positions the positions that describe the subsets
* @return data object representing the DLX problem
*/
static constexpr data init(const position_array<NumNodes> &positions) noexcept {
data d{};
// Create the header data.
for (index i = 0; i < HeaderSize; ++i) {
//d.T[i] = columnTypes[i];
d.U[i] = i;
d.D[i] = i;
d.C[i] = i;
d.S[i] = 0;
d.RM[i] = NumRows;
}
// Do the L-R linking of the columns.
for (index i = 0; i <= header; ++i) {
d.R[i] = (i + 1) % HeaderSize;
d.L[i] = (i - 1 + HeaderSize) % HeaderSize;
}
// Now handle the rows.
index rowIdx = 0;
while (rowIdx < NumNodes) {
// Find the start and end positions of this row.
const index rowNumber = positions[rowIdx].first;
const index rowStartIdx = rowIdx;
index rowEndIdx = rowIdx;
while (rowEndIdx < NumNodes && positions[rowEndIdx].first == rowNumber)
++rowEndIdx;
// The row goes from [rowStartIdx, rowEndIdx), so link the nodes.
for (index idx = rowStartIdx; idx < rowEndIdx; ++idx) {
const auto[row, column] = positions[idx];
const index posIdx = HeaderSize + idx;
// Set the row and column for the new node.
d.C[posIdx] = column;
d.RM[posIdx] = rowNumber;
// Link to bottom of column, with up pointing to col's previous up, and down to header.
d.U[posIdx] = d.U[column];
d.D[posIdx] = column;
// Adjust the header to include the new entry.
d.D[d.U[column]] = posIdx;
d.U[column] = posIdx;
// Add 1 to the size of the column.
++d.S[column];
// Link left to previous, if there is one, and right to first in row.
d.L[posIdx] = idx > rowStartIdx ? posIdx - 1 : posIdx;
d.R[posIdx] = rowStartIdx + HeaderSize;
// Adjust right and left links of this node to point to this node.
d.L[d.R[posIdx]] = posIdx;
d.R[d.L[posIdx]] = posIdx;
}
rowIdx = rowEndIdx;
}
return std::move(d);
}
/**
* Create the solution and initialize it.
* @return empty solution array
*/
static constexpr solution init_solution() noexcept {
solution sol{};
for (auto &s: sol)
s = false;
return std::move(sol);
}
public:
DLX() = delete;
/**
* Given a set of "positions" of the form (r,c) indicating that element c is in subset r, run the
* exact cover problem using Knuth's dancing links algorithm.
*
* @param positions list of the positions describing the subsets
* @return the first solution found if one exists, or nullopt otherwise
*/
static constexpr std::optional<solution> run(const position_array<NumNodes> &positions) noexcept {
return find_solution(init(positions), init_solution());
}
/**
* Given a set of "positions" of the form (r,c) indicating that element c is in subset r,
* and a set of rows to force into the final solution, run the exact cover problem using Knuth's
* dancing links algorithm.
*
* @param positions list of the positions describing the subsets
* @param fixed_rows a list of fixed rows (not offset by the header)
* @return the first solution found if one exists, or nullopt otherwise
*/
template<size_t NumFixedRows>
static constexpr std::optional<solution> run(const position_array<NumNodes> &positions,
const std::array<size_t, NumFixedRows> &fixed_rows) noexcept {
auto state = init(positions);
// Initialize the solution.
auto sol = init_solution();
for (const auto row: fixed_rows)
useRow(state, row + HeaderSize, sol);
return find_solution(state, sol);
}
};
}