View difference between Paste ID: xxAHUmyp and qy483BEi
SHOW: | | - or go back to the newest paste.
1
/*
2
 * Filename     : queens.cpp
3
 * Author       : Kevin F
4
 * License      : CopyLeft, Feb 2017
5
 * Eight Queens : Find solutions which have N non-attacking queens placed on NxN chess board
6
 * Algorithm	: Rooted at 1,1 simultaneously add row and column to problem space
7
 * 				: tracking the suitable queen locations in sets avail_x and avail_y
8
 * 				: Each queen is validated for diagonal attacks prior to adding to board
9
 * Input        : Adjust BOARD_SIZE to change the size of the N
10
 * Output       : ASCII printed board and runtime statistics
11
 * Requires     : C++11
12
 * Compilation  : g++ -std=c++11 -O3 -o queens queens.cpp
13
 * Source       : http://bit.ly/code_golf_queens
14
 * Speed        : Found: 92 solutions in: 0.002582 seconds using: 2747 recursions
15
 * Without Opt  : Found: 92 solutions in: 0.132239 seconds using: 139049 recursions
16
*/
17
18
#include <algorithm>
19
#include <iostream>
20
#include <set>
21
#include <cmath>
22
#include <vector>
23
#include <string>
24
25
using std::cout;
26
using std::vector;
27
using std::set;
28
29
// TODO: implement cli argument
30
const uint8_t BOARD_SIZE = 8;
31
32
struct queen_t {
33
    uint8_t x;
34
    uint8_t y;
35
};
36
37
static uint32_t num_recursions;
38
39
/* Forward declaration for validate_and_continue */
40
void find_more_solutions(vector<queen_t> &board, set<uint8_t> &avail_x,
41
    set<uint8_t> &avail_y, uint8_t cur_iteration,
42
    vector< vector<queen_t> > &solutions);
43
44
void print_board(const vector<queen_t> &board){
45
46
    /* Sample Output
47
     *  |12345678
48
     * 1|xxxxxQxx
49
     * 2|xxxQxxxx
50
     * 3|xxxxxxQx
51
     * 4|Qxxxxxxx
52
     * 5|xxxxxxxQ
53
     * 6|xQxxxxxx
54
     * 7|xxxxQxxx
55
     * 8|xxQxxxxx
56
    */
57
58
    /* Generate empty rows */
59
    vector<std::string> rows = {" |"};
60
    for(int i=1; i <= BOARD_SIZE; i++){
61
        rows[0].append(std::to_string(i));
62
        rows.push_back(std::to_string(i) + "|" + std::string(BOARD_SIZE, 'x'));
63
    }
64
65
    /* Fill rows with queens */
66
    /* TODO: this produces incorrect results with n > 9 due to width of #| */
67
    for_each(board.begin(), board.end(), [&](const queen_t queen){
68
        rows[queen.y].replace(queen.x+1, 1, "Q"); // +1 to skip #|
69
    });
70
71
    for(int i=0; i <= BOARD_SIZE; i++){
72
        cout << rows[i] << "\n";
73
    }
74
}
75
76
/*
77
 * Slope can be calculated as rise/run = (Y2-Y1)/(X2-X1)
78
 * A slope of 1 (perfect diagonal) implies (Y2-Y1) = (X2-X1)
79
*/
80
bool diagonal_slope(const queen_t &first_queen, const queen_t &second_queen){
81
82
    /* May be sent empty queens */
83
    if (first_queen.x == 0 || second_queen.x == 0){
84
        return false;
85
    }
86
87
    return abs(second_queen.y - first_queen.y) == abs(second_queen.x - first_queen.x);
88
}
89
90
/* Return true when new queen(s) trigger a diagonal attack */
91
bool can_attack(const vector<queen_t> &board, const queen_t &first_queen,
92
        const queen_t &second_queen){
93
94
    for(const queen_t &queen : board){
95
        if (diagonal_slope(first_queen, queen) == true ||
96
                diagonal_slope(queen, second_queen) == true){
97
            return true;
98
        }
99
    }
100
101
    return diagonal_slope(first_queen, second_queen);
102
}
103
104
/* If 0..2 queens added are valid, continue recursion */
105
void validate_and_continue(vector<queen_t> &board, set<uint8_t> &avail_x,
106
        set<uint8_t> &avail_y, uint8_t cur_iteration,
107
        const queen_t first_queen, const queen_t second_queen,
108
        vector< vector<queen_t> > &solutions){
109
110
    /* Early prune any entries that produce diagonal attacks */
111
    if (can_attack(board, first_queen, second_queen) == true){
112
        return;
113
    }
114
115
    /* Update availability lists and create a new board */
116
    set<uint8_t> new_avail_x = avail_x;
117
    set<uint8_t> new_avail_y = avail_y;
118
    vector<queen_t> new_board = board;
119
120
    if (first_queen.x != 0){
121
        new_avail_x.erase(first_queen.x);
122
        new_avail_y.erase(first_queen.y);
123
        new_board.push_back(first_queen);
124
    }
125
126
    if (second_queen.x != 0){
127
        new_avail_x.erase(second_queen.x);
128
        new_avail_y.erase(second_queen.y);
129
        new_board.push_back(second_queen);
130
    }
131
132
    /* Recurse with new modified copy of data */
133
    find_more_solutions(new_board, new_avail_x, new_avail_y,
134
        cur_iteration + 1, solutions);
135
}
136
137
/* Iterate through 1..BOARD_SIZE queens based on X,Y availability lists */
138
void find_more_solutions(vector<queen_t> &board, set<uint8_t> &avail_x,
139
        set<uint8_t> &avail_y, uint8_t cur_iteration,
140
        vector< vector<queen_t> > &solutions) {
141
142
    //cout << "CurIter: " << static_cast<uint16_t>(cur_iteration) << \
143
		", queens placed: " << board.size() << "\n";
144
145
    num_recursions ++;
146
147
    /* Completion conditions */
148
    uint8_t queens_left = BOARD_SIZE - board.size();
149
    if (cur_iteration > BOARD_SIZE){
150
        if (queens_left == 0 && avail_x.size() == 0 && avail_y.size() == 0) {
151
			//cout << "Adding solution, recursions so far: " << num_recursions << "\n";
152
			solutions.push_back(board);
153
        }
154
155
        return;
156
    }
157
158
    /* Current iteration is now available for X and Y positions */
159
    avail_x.insert(cur_iteration);
160
    avail_y.insert(cur_iteration);
161
162
    uint8_t rounds_left = BOARD_SIZE - cur_iteration + 1; // including this round
163
    uint8_t queens_to_add = 0;
164
    if (queens_left >= (rounds_left * 2)) {
165
        /* Optimize for perfect 2 and skip 3+ saves ~5000 recursions */
166
        queens_to_add = ceil((double)queens_left / rounds_left);
167
    } else if (queens_left > ((rounds_left - 1) * 2)) {
168
        /* Avoid adding 0 queens this round saves ~500 recursions */
169
        queens_to_add = 1;
170
    }
171
172
    /* Add 0, 1, or 2 queens this round */
173
    for (; queens_to_add <= std::min(queens_left, (uint8_t)2); queens_to_add ++) {
174
        switch (queens_to_add) {
175
        case 1:
176
            /* Possible (X,cur_iteration) pairs */
177
            for_each(avail_x.begin(), avail_x.end(), [&](const uint8_t cur_x) {
178
                validate_and_continue(board, avail_x, avail_y, cur_iteration,
179
                {cur_x, (uint8_t)cur_iteration}, {}, solutions);
180
            });
181
182
            /* Possible (cur_iteration,Y) pairs */
183
            for_each(avail_y.begin(), avail_y.end(), [&](const uint8_t cur_y) {
184
                /* Equivalent to X = cur_iteration case above */
185
                if (cur_y == cur_iteration) {
186
                    return;
187
                }
188
189
                validate_and_continue(board, avail_x, avail_y, cur_iteration,
190
					{(uint8_t)cur_iteration, cur_y}, {}, solutions);
191
            });
192
            break;
193
        case 2:
194
            /*
195
             * Example outcomes for cur_iteration = 2
196
             * x = 1, y = 1... queens 1,2 and 2,1 ** valid
197
             * x = 1, y = 2....queens 1,2 and 2,2 ** skip in Y
198
             * x = 2, y = 1... queens 2,2 and 2,1 ** skip in X
199
             * x = 2, y = 2... queens 2,2 and 2,2 ** skip in X
200
            */
201
202
            for_each(avail_x.begin(), avail_x.end(), [&](const uint8_t cur_x) {
203
                /* Don't place a queen on a diagonal, would attack 2nd queen */
204
                if (cur_x == cur_iteration) {
205
                    return;
206
                }
207
208
                /* Possible (cur_iteration,Y) pairs */
209
                for_each(avail_y.begin(), avail_y.end(), [&](const uint8_t cur_y) {
210
211
                    /* Don't place a queen on a diagonal, would attack 1st queen */
212
                    if (cur_y == cur_iteration) {
213
                        return;
214
                    }
215
216
                    validate_and_continue(board,
217
                        avail_x, avail_y, cur_iteration,
218
                        {cur_x, (uint8_t)cur_iteration},
219
                        {(uint8_t)cur_iteration, cur_y}, solutions);
220
                });
221
            });
222
            break;
223
        default:
224
            /* Place 0 queens this round, impossible >2 without collision */
225
            validate_and_continue(board, avail_x, avail_y, cur_iteration,
226
				{}, {}, solutions);
227
            break;
228
        }
229
    }
230
}
231
232
int main(){
233
234
    cout << "Calculating solutions...\n";
235
236
    vector< vector<queen_t> > solutions;
237
    vector<queen_t> board;
238
    set<uint8_t> avail_x;
239
    set<uint8_t> avail_y;
240
    clock_t start_time = clock();
241
242
    /*
243
    * Recurse through possible outcomes that wont attack in row/column
244
    * Validate each placed queen against diagonal attacks via slope = 1
245
    */
246
    find_more_solutions(board, avail_x, avail_y, 1, solutions);
247
248
    cout << "Found: " << solutions.size() << " solutions in: " <<
249
        ((double_t)(clock() - start_time)/CLOCKS_PER_SEC) <<
250
        " seconds using: " << num_recursions << " recursions\n";
251
252
    print_board(solutions.front());
253
}