-
Notifications
You must be signed in to change notification settings - Fork 1
/
countdown.cpp
386 lines (330 loc) · 10.1 KB
/
countdown.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
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
383
384
385
/*
* countdown.cpp
*
* Solves the Countdown numbers game (from UK Channel 4).
*/
#include <iostream>
#include <array>
#include <vector>
#include <cstdlib>
#include <ctime>
/*
* Operator type enum.
*/
enum class operator_type {
add,
subtract,
multiply,
divide
};
/*
* Computation step recording.
*/
class step {
public:
int _result;
operator_type _op_type;
int _operand1;
int _operand2;
step(int result = 0, operator_type op_type = operator_type::add, int operand1 = 0, int operand2 = 0) {
_result = result;
_op_type = op_type;
_operand1 = operand1;
_operand2 = operand2;
}
};
/*
* Countdown computation class.
*/
class countdown {
public:
countdown(int target, int tiles[6]) {
_target = target;
for (auto i = 0; i < 6; i++) {
_tiles[i] = tiles[i];
}
_closest = 0;
_fewest_steps = 7;
_rounds = 0;
_working_steps_sz = 0;
_best_steps_sz = 0;
};
auto compute() -> void {
permute_all(_tiles, 6);
}
auto get_best_steps() -> const std::vector<step> {
std::vector<step> s;
for (auto i = 0; i < _best_steps_sz; ++i) {
s.emplace_back(_best_steps[i]);
}
return s;
}
auto get_closest() -> int {
return _closest;
}
auto get_rounds() -> int {
return _rounds;
}
private:
std::array<int, 6> _tiles; // The tiles we have to use
int _target; // The number are we trying to hit
int _closest; // The closest we've got to the target number
int _rounds; // The number of rounds we do to reach our result
int _fewest_steps; // Smallest number of steps so far
std::array<step, 6> _working_steps; // The set of working steps we have in any given iteration
int _working_steps_sz; // Number of steps in our working set of steps
std::array<step, 6> _best_steps; // Best set of steps that we found
int _best_steps_sz; // Number of steps in the best solution we currently have
inline auto permute_add(const std::array<int, 6> &v, int v_sz) -> void;
inline auto permute_subtract(const std::array<int, 6> &v, int v_sz) -> void;
inline auto permute_multiply(const std::array<int, 6> &v, int v_sz) -> void;
inline auto permute_divide(const std::array<int, 6> &v, int v_sz) -> void;
auto permute_common(int new_val, operator_type op, const std::array<int, 6> &v, int v_sz, int i, int j) -> void;
/*
* Run all the possible permutations for the current input vector.
*/
auto permute_all(const std::array<int, 6> &v, int v_sz) -> void {
_rounds++;
permute_add(v, v_sz);
permute_subtract(v, v_sz);
permute_multiply(v, v_sz);
permute_divide(v, v_sz);
}
};
/*
* Starting grid.
*/
int starting_grid[] = {
100, 75, 50, 25,
10, 10, 9, 9, 8, 8, 7, 7, 6, 6,
5, 5, 4, 4, 3, 3, 2, 2, 1, 1
};
/*
* Handle the common operations associated with any permutation.
*/
auto countdown::permute_common(int new_val, operator_type op, const std::array<int, 6> &v, int v_sz, int i, int j) -> void {
_working_steps[_working_steps_sz++] = step(new_val, op, v[i], v[j]);
if (abs(_target - new_val) < abs(_target - _closest)) {
for (auto i = 0; i < _working_steps_sz; ++i) {
_best_steps[i] = _working_steps[i];
}
_best_steps_sz = _working_steps_sz;
_closest = new_val;
if (new_val == _target) {
_fewest_steps = _working_steps_sz;
_working_steps_sz--;
return;
}
}
/*
* If another iteration can still result in a shorter solution than the best
* we've found so far then proceed.
*/
if ((_fewest_steps - 1) > _working_steps_sz) {
/*
* If we have more than 2 values in our input vector then we can iterate further,
* combining our new result with all unused inputs.
*/
if (v_sz > 2) {
std::array<int, 6> new_v;
auto idx = 0;
new_v[idx++] = new_val;
for (auto k = 0; k < v_sz; ++k) {
if ((k != i) && (k != j)) {
new_v[idx++] = v[k];
}
}
permute_all(new_v, v_sz - 1);
}
}
_working_steps_sz--;
}
/*
* Run permutations of an input array for addition.
*/
auto countdown::permute_add(const std::array<int, 6> &v, int v_sz) -> void {
/*
* We want to find all the permutations of additions within the input array.
* Addition is commutative so (a + b) = (b + a), meaning we don't need to examine
* any scenarios we've already seen.
*/
for (auto i = 0; i < (v_sz - 1); ++i) {
for (auto j = i + 1; j < v_sz; ++j) {
/*
* Compute a permutation, and check if it results in an exact match.
*/
auto new_val = v[i] + v[j];
permute_common(new_val, operator_type::add, v, v_sz, i, j);
}
}
}
/*
* Run permutations of an input array for subtraction.
*/
auto countdown::permute_subtract(const std::array<int, 6> &v, int v_sz) -> void {
for (auto i = 0; i < v_sz; ++i) {
for (auto j = 0; j < v_sz; ++j) {
/*
* We can't use the same tile or intermediate value twice.
*/
if (i == j) {
continue;
}
auto new_val = v[i] - v[j];
/*
* If our subtraction results in a zero value then it's a dead end. Similarly,
* any negative value can always be handled as a positive one but with a
* subtraction rather than an addition.
*/
if (new_val <= 0) {
continue;
}
/*
* If our subtraction results in the same value as our subtrahend then we've also
* hit a dead end because we've not introduced any useful new intermediate value.
*/
if (new_val == v[j]) {
continue;
}
permute_common(new_val, operator_type::subtract, v, v_sz, i, j);
}
}
}
/*
* Run permutations of an input array for multiplication.
*/
auto countdown::permute_multiply(const std::array<int, 6> &v, int v_sz) -> void {
/*
* We want to find all the permutations of multiplications within the input array.
* Multiplication is commutative so (a * b) = (b * a), meaning we don't need to examine
* any scenarios we've already seen.
*/
for (auto i = 0; i < (v_sz - 1); ++i) {
/*
* Multiplying by 1 doesn't get us anywhere.
*/
if (v[i] == 1) {
continue;
}
for (auto j = i + 1; j < v_sz; ++j) {
/*
* Multiplying by 1 doesn't get us anywhere.
*/
if (v[j] == 1) {
continue;
}
auto new_val = v[i] * v[j];
permute_common(new_val, operator_type::multiply, v, v_sz, i, j);
}
}
}
/*
* Run permutations of an input array for division.
*/
auto countdown::permute_divide(const std::array<int, 6> &v, int v_sz) -> void {
for (auto i = 0; i < v_sz; ++i) {
for (auto j = 0; j < v_sz; ++j) {
/*
* We can't use the same tile or intermediate value twice.
*/
if (i == j) {
continue;
}
/*
* Dividing by 1 doesn't get us anywhere.
*/
if (v[j] == 1) {
continue;
}
auto new_val = v[i] / v[j];
/*
* If our division does not result in an exact quotient then we can't make
* any more progress down this particular path.
*/
if (v[i] % v[j]) {
continue;
}
/*
* If our division results in the same value as our divisor then we've also
* hit a dead end because we've not introduced any useful new intermediate value.
*/
if (new_val == v[j]) {
continue;
}
permute_common(new_val, operator_type::divide, v, v_sz, i, j);
}
}
}
/*
* Output a step vector.
*/
auto dump_steps(const std::vector<step> &s) -> void {
auto sz = static_cast<int>(s.size());
for (auto i = 0; i < sz; ++i) {
auto &st = s[i];
std::cout << st._operand1;
switch (st._op_type) {
case operator_type::add:
std::cout << " + ";
break;
case operator_type::subtract:
std::cout << " - ";
break;
case operator_type::multiply:
std::cout << " * ";
break;
case operator_type::divide:
std::cout << " / ";
break;
}
std::cout << st._operand2 << " = " << st._result << '\n';
}
std::cout << '\n';
}
/*
* Entry point.
*/
auto main(int argc, char **argv) -> int {
int v[6];
/*
* Randomize our tiles.
*/
srandom(time(NULL));
std::cout << "Numbers are:";
/*
* Pick 6 unique random tiles.
*/
for (auto i = 0; i < 6; ++i) {
int time;
do {
time = random() % 24;
} while (starting_grid[time] < 0);
v[i] = starting_grid[time];
starting_grid[time] = -1;
std::cout << " " << v[i];
}
/*
* Pick a random target in the range 101 to 999.
*/
auto target = (random() % 899) + 101;
std::cout << ", target is: " << target << "\n\n";
/*
* Set up the problem and compute the best solution.
*/
countdown c(target, v);
c.compute();
/*
* Output the results.
*/
std::cout << "after: ";
std::cout << c.get_rounds();
std::cout << " rounds, ";
auto closest = c.get_closest();
if (closest == target) {
std::cout << "solved:\n\n";
} else {
std::cout << abs(target - closest) << " away:\n\n";
}
dump_steps(c.get_best_steps());
return 0;
}