kevinf28

Lets Play Code Golf: Queens

Apr 9th, 2017
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * Filename     : queens.cpp
  3.  * Author       : Kevin Folz <kevinfolz@gmail.com>
  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. }
Add Comment
Please, Sign In to add comment