-
Notifications
You must be signed in to change notification settings - Fork 0
/
DisasterLinks.cpp
312 lines (255 loc) · 12.2 KB
/
DisasterLinks.cpp
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
/**
* Author: Alexander G. Lopez
* File: DisasterLinks.cpp
* --------------------------
* This file contains the implementation of Algorithm X via Dancing Links as applied to a Disaster
* Planning problem. For more details on the thought process behind the implmenetation, please see
* the DancingLinks.h file or the readme. Comments are detailed for my own understanding and because
* some of the implementation is complicated, especially the building of the dancing links grid.
*/
#include <cmath>
#include <limits.h>
#include "DisasterLinks.h"
namespace DancingLinks {
/* * * * * * * * * * * * * Free Functions for DancingLinks Namespace * * * * * * * * * * * * */
bool hasOverlappingCover(DisasterLinks& links, int numSupplies, std::set<std::string>& selectedOptions) {
return links.isDisasterReady(numSupplies, selectedOptions);
}
std::set<std::set<std::string>> getAllOverlappingCovers(DisasterLinks& links, int numSupplies) {
return links.getAllDisasterConfigurations(numSupplies);
}
/* * * * * * * * * * * * * Algorithm X via Dancing Links Implementation * * * * * * * * * * * * */
bool DisasterLinks::isDisasterReady(int numSupplies, std::set<std::string>& suppliedCities) {
if (numSupplies < 0) {
error("Negative supply quantity is impossible.");
}
if (numItemsAndOptions_ == 0) {
return true;
}
return isCovered(numSupplies, suppliedCities);
}
bool DisasterLinks::isCovered(int numSupplies, std::set<std::string>& suppliedCities) {
if (table_[0].right == 0 && numSupplies >= 0) {
return true;
}
if (numSupplies <= 0) {
return false;
}
// Choose the city that appears the least across all sets because that will be hard to cover.
int chosenIndex = chooseIsolatedCity();
/* Try to cover this city by first supplying the most connected city nearby. Try cities with
* successively fewer connections then if all else fails supply the isolated city itself.
*/
for (int cur = grid_[chosenIndex].down; cur != chosenIndex; cur = grid_[cur].down) {
std::string supplyLocation = coverCity(cur);
if (isCovered(numSupplies - 1, suppliedCities)) {
// Only add to the output if successful and be sure to cleanup in case it runs again.
suppliedCities.insert(supplyLocation);
uncoverCity(cur);
return true;
}
// This cleanup is in case of failed choices. Try another starting supply location.
uncoverCity(cur);
}
return false;
}
std::set<std::set<std::string>> DisasterLinks::getAllDisasterConfigurations(int numSupplies) {
if (numSupplies < 0) {
error("Negative supply count.");
}
std::set<std::string> suppliedCities {};
std::set<std::set<std::string>> allConfigurations = {};
fillConfigurations(numSupplies, suppliedCities, allConfigurations);
return allConfigurations;
}
void DisasterLinks::fillConfigurations(int numSupplies,
std::set<std::string>& suppliedCities,
std::set<std::set<std::string>>& allConfigurations) {
if (table_[0].right == 0 && numSupplies >= 0) {
allConfigurations.insert(suppliedCities);
return;
}
if (numSupplies <= 0) {
return;
}
int chosenIndex = chooseIsolatedCity();
for (int cur = grid_[chosenIndex].down; cur != chosenIndex; cur = grid_[cur].down) {
std::string supplyLocation = coverCity(cur);
suppliedCities.insert(supplyLocation);
fillConfigurations(numSupplies - 1, suppliedCities, allConfigurations);
suppliedCities.erase(supplyLocation);
// This cleanup is in case of failed choices. Try another starting supply location.
uncoverCity(cur);
}
}
int DisasterLinks::chooseIsolatedCity() const {
int min = INT_MAX;
int chosenIndex = 0;
int head = 0;
for (int cur = table_[0].right; cur != head; cur = table_[cur].right) {
if (grid_[cur].topOrLen < min) {
chosenIndex = cur;
min = grid_[cur].topOrLen;
}
}
return chosenIndex;
}
std::string DisasterLinks::coverCity(int indexInOption) {
/* Be sure to leave the row of the option we supply unchanged. Splice these cities out of all
* other options in which they can be found above and below the current row.
*/
int i = indexInOption;
std::string result = "";
do {
int top = grid_[i].topOrLen;
if (top <= 0) {
/* We are always guaranteed to pass the spacer tile so we will collect the name of the
* city we have chosen to supply to prove our algorithm chose correctly.
*/
result = table_[std::abs(top)].name;
} else {
hideCityCol(i);
table_[table_[top].left].right = table_[top].right;
table_[table_[top].right].left = table_[top].left;
}
i = grid_[i].right;
} while (i != indexInOption);
return result;
}
void DisasterLinks::uncoverCity(int indexInOption) {
/* To uncover a city we take the supplies away from the option in which we found this city. We
* then must go up and down for every city covered by this supply location and put the cities
* back in all the other sets. Original row was not altered so no other restoration necessary.
*/
indexInOption = grid_[indexInOption].left;
int i = indexInOption;
do {
int top = grid_[i].topOrLen;
if (top > 0) {
table_[table_[top].left].right = top;
table_[table_[top].right].left = top;
unhideCityCol(i);
}
i = grid_[i].left;
} while (i != indexInOption);
}
void DisasterLinks::hideCityCol(int indexInCol) {
for (int i = grid_[indexInCol].down; i != indexInCol; i = grid_[i].down) {
cityItem cur = grid_[i];
grid_[cur.right].left = cur.left;
grid_[cur.left].right = cur.right;
}
}
void DisasterLinks::unhideCityCol(int indexInCol) {
for (int i = grid_[indexInCol].up; i != indexInCol; i = grid_[i].up) {
cityItem cur = grid_[i];
grid_[cur.right].left = i;
grid_[cur.left].right = i;
}
}
/* * * * * * * * * * * Constructor and Building of Dancing Links Network * * * * * * * * * * * */
DisasterLinks::DisasterLinks(const std::map<std::string, std::set<std::string>>& roadNetwork)
: table_(),
grid_(),
numItemsAndOptions_(0) {
// We will set this up for a reverse build of column links for a given item.
std::unordered_map<std::string,int> columnBuilder = {};
std::vector<std::pair<std::string,int>> connectionSizes = {};
// We need to start preparing items in the grid immediately after the headers.
initializeHeaders(roadNetwork, connectionSizes, columnBuilder);
/* The second pass will fill in the columns and keep the headers and all elements appropriately
* updated. We can hang on to helpful index info.
*/
initializeItems(roadNetwork, connectionSizes, columnBuilder);
}
void DisasterLinks::initializeHeaders(const std::map<std::string, std::set<std::string>>& roadNetwork,
std::vector<std::pair<std::string,int>>& connectionSizes,
std::unordered_map<std::string,int>& columnBuilder) {
table_.push_back({"", 0, 1});
grid_.push_back({0,0,0,0,1});
int index = 1;
// The first pass will set up the name headers and the column headers in the two vectors.
for (const auto& city : roadNetwork) {
// Add one to the connection size because we will add a city to its own connections later.
connectionSizes.push_back({city.first, roadNetwork.at(city.first).size() + 1});
// We need to set up multiple columns, so begin tracking the previous item for a column.
columnBuilder[city.first] = index;
table_.push_back({city.first, index - 1, index + 1});
table_[0].left++;
grid_[0].left++;
// Add the first headers for the item vector. They need count up and down.
grid_.push_back({0, index, index, index - 1, index + 1});
numItemsAndOptions_++;
index++;
}
/* We want the most isolated cities to try to cover themselves by supplying adjacent neighbor
* with the most other connections. This is not garunteed to work every time, but most times
* this is the best strategy to not waste time supplying very isolated cities first. So organize
* the options by most connections to fewest.
*/
std::sort(connectionSizes.begin(), connectionSizes.end(), [](auto &left, auto &right) {
return left.second > right.second;
});
table_[table_.size() - 1].right = 0;
grid_[grid_.size() - 1].right = 0;
}
void DisasterLinks::initializeItems(const std::map<std::string, std::set<std::string>>& roadNetwork,
const std::vector<std::pair<std::string,int>>& connectionSizes,
std::unordered_map<std::string,int>& columnBuilder) {
int previousSetSize = grid_.size();
int index = grid_.size();
for (const auto& [city, connectionSize] : connectionSizes) {
// This algorithm includes a city in its own set of connections.
std::set<std::string> connections = roadNetwork.at(city);
connections.insert(city);
int setSize = connections.size();
/* We will know which supplying city option an item is in by the spacerTitle.
* lookupTable[abs(-grid_[columnBuilder[city]].down)] will give us the name of the option
* of that row. This accesses the last city in a column's down field to get the header.
*/
grid_.push_back({-grid_[columnBuilder[city]].down, // Negative index of city as option.
index - previousSetSize, // First item in previous option
index + setSize, // Last item in current option
index,
index + 1});
// Manage column pointers for items connected across options. Update index.
index = initializeColumns(connections, columnBuilder, index);
previousSetSize = setSize;
}
grid_.push_back({INT_MIN, index - previousSetSize, 0, index - 1, INT_MIN});
}
int DisasterLinks::initializeColumns(const std::set<std::string>& connections,
std::unordered_map<std::string,int>& columnBuilder,
int index) {
int spacerIndex = index;
for (const auto& c : connections) {
// Circular lists give us access to header with down field of last city in a column.
grid_[grid_[columnBuilder[c]].down].topOrLen++;
index++;
// A single item in a circular doubly linked list points to itself.
grid_.push_back({grid_[columnBuilder[c]].down, index, index, index - 1, index + 1});
/* Now we need to handle building the up and down pointers for a column of items.
* We also must make sure to keep the most recent element pointing down to the
* first of a column because this is circular. The first elem in the column must
* also point up to the most recently added element because this is circular.
*/
// This is the necessary adjustment to the column header's up field for a given item.
grid_[grid_[columnBuilder[c]].down].up = index;
// The current node is now the new tail in a vertical circular linked list for an item.
grid_[index].up = columnBuilder[c];
grid_[index].down = grid_[columnBuilder[c]].down;
// Update the old tail to reflect the new addition of an item in its option.
grid_[columnBuilder[c]].down = index;
// Similar to a previous/current coding pattern but in an above/below column.
columnBuilder[c] = index;
}
/* Every option "row" is a left-right circular linked list. This is how we recursively cover
* Cities by removing them as items only. A city that is adjacent to a supplied city may still
* receive supplies to cover other cities as an option. This is how we achieve that. This is
* a significant variation from Knuth's DLX.
*/
grid_[index].right = spacerIndex;
grid_[spacerIndex].left = index;
return ++index;
}
} // namespace DancingLinks