Input: Eight lines with eight characters each, free (.
) or reserved (*
)
This is the well known Eight Queens Problem. The goal is to place eight queens on a chessboard in a way they don't attack each other. Additionally some fields are already reserved which means we cannot place any queens there.
We know that in each row can only be one queen. Otherwise they would attack each other. So we store the input lines as Vec
s and iterate from one row to the next and decide where to place a queen. This decision happens on the basis of the already placed queens and their attack range.
We have to keep track of columns, positive diagonals and negative diagonals. If for example in the first row a queen is placed at column col
variable we store a
As we observe, to keep track of the reserved columns is pretty straight forward. But how do we keep track of diagonals?
Because a queen's attack range can be a positive or negative diagonal, we also cannot place any other queen in the range of either one of these diagonals.
As for positive diagonal pos_diag
, we can see a pattern in a
So if we place a queen at e.g. row 1 column 5 (
The same logic applies for negative diagonals neg_diag
, except that we subtract the column index from the row index
Let's say the queen we placed before at row 1 column 5 (
The last thing we have to remember is that in the input we have some fields denoted with *
which are reserved. Meaning we can't place queens in these fields. But these fields do not prevent queens from attacking each other.
We take the input string and put it into a 2-D vector Vec<Vec<char>>
, where the outer vector holds vectors representing the rows and the inner vector holds characters denoting the columns.
Then we create three HashSet
s for col
, pos_diag
and neg_diag
. In this way we keep track of the attack ranges so we know where to not place queens. We choose HashSet
in order to ensure that the integers are unique.
Then we create a recursive function and pass it the 2-D vector representing the chessboard, the index of the first row (HashSet
s.
The base case of this recursive function is r == 8
. So when we reach row 8 we know that we are at the end of the chessboard and we have another combination of queens.
On each recursive function call we iterate over a range from *
.
Then we check:
- Does
col
contain the current column index? - Does
pos_diag
contain the result of$r + c$ where$c$ is the current field? - Does
neg_diag
contain the result of$r - c$ where$c$ is the current field?
If any of these conditions evaluate to true
then we just continue
with the next field.
If not then we place a queen. Meaning we insert the appropriate integer into the HashSet
s:
-
col
$\rightarrow c$ where$c$ is the current field's column index -
pos_diag
$\rightarrow r + c$ -
neg_diag
$\rightarrow r - c$
Then we recursively call the function where we increment result
. result
is the variable which will hold the number of different combinations of placing queens.
After the recursive function call we backtrack. Basically undoing what we inserted into the HashSet
s before. This is to ensure that we get all possible combinations.
In Rust 🦀 code:
use std::collections::HashSet as HS;
fn main() {
let board: Vec<Vec<char>> = std::io::read_to_string(std::io::stdin())
.unwrap()
.lines()
.map(|s| s.chars().collect())
.collect();
let (mut col, mut pos_diag, mut neg_diag) = (HS::new(), HS::new(), HS::new());
println!(
"{}",
queens(&board, 0, &mut col, &mut pos_diag, &mut neg_diag)
);
}
fn queens(
board: &Vec<Vec<char>>,
r: i8,
col: &mut HS<i8>,
pos_diag: &mut HS<i8>,
neg_diag: &mut HS<i8>,
) -> i8 {
let mut res = 0;
if r == 8 { return 1; }
for c in 0..8 {
if board[r as usize][c as usize] == '*'
|| col.contains(&c)
|| pos_diag.contains(&(r + c))
|| neg_diag.contains(&(r - c))
{
continue;
}
col.insert(c); pos_diag.insert(r + c); neg_diag.insert(r - c);
res += queens(board, r + 1, col, pos_diag, neg_diag);
col.remove(&c); pos_diag.remove(&(r + c)); neg_diag.remove(&(r - c));
}
res
}