Skip to content

Commit 184c997

Browse files
committed
feat(codewars): another 1kuy task
1 parent b765af0 commit 184c997

File tree

5 files changed

+282
-0
lines changed

5 files changed

+282
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

codewars/n-queens-problem/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "n-queens-problem"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
rand = "0.8.5"

codewars/n-queens-problem/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# N queens problem (with one mandatory queen position) - challenge version
2+
3+
[codewars](https://www.codewars.com/kata/5985ea20695be6079e000003/train/rust)
4+
5+
The eight queens puzzle is the problem of placing eight chess queens on an 8×8 chessboard so that no two queens threaten each other. Thus, a solution requires that no two queens share the same row, column or diagonal. The eight queens puzzle is an example of the more general N queens problem of placing N non-attacking queens on an N×N chessboard. You can read about the problem on its Wikipedia page: Eight queens puzzle.
6+
7+
You will receive a (possibly large) number N and have to place N queens on a N×N chessboard, so that no two queens attack each other. This requires that no two queens share the same row, column or diagonal. You will also receive the mandatory position of one queen. This position is given 0-based with 0 <= row < N and 0 <= col < N. The coordinates {0, 0} are in the top left corner of the board. For many given parameters multiple solutions are possible. You have to find one of the possible solutions, all that fit the requirements will be accepted.
8+
9+
You have to return the solution board as a string, indicating empty fields with '.' (period) and Queens with 'Q' (uppercase Q), ending each row with '\n'.
10+
11+
If no solution is possible for the given parameters, return None.
12+
13+
Notes on Rust version:
14+
15+
input parameters are size and (mandatory_column, mandatory_row)
16+
there are 8 tests for very small boards (N <= 10)
17+
there are 8 tests for cases without solution
18+
there are 5 tests for small boards (10 < N <= 50)
19+
there are 5 tests for medium boards (100 < N <= 500)
20+
there are 5 tests for large boards (500 < N <= 1000)
21+
Example:
22+
23+
For input of size=8, mandatory column=3 and mandatory row=0, your solution could return:
24+
25+
"...Q....\n......Q.\n..Q.....\n.......Q\n.Q......\n....Q...\nQ.......\n.....Q..\n"
26+
giving the following board:
27+
28+
...Q....
29+
......Q.
30+
..Q.....
31+
.......Q
32+
.Q......
33+
....Q...
34+
Q.......
35+
.....Q..
36+
(Other solutions to this example are possible and accepted. The mandatory queen has to be in its position, in the example in the first row at col=3, row=0.)
37+

codewars/n-queens-problem/src/lib.rs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
use rand::{thread_rng, Rng};
2+
3+
fn shuffle<T>(arr: &mut [T]) {
4+
let n = arr.len();
5+
let mut rng = thread_rng();
6+
for i in 0..n - 1 {
7+
let pos = rng.gen_range(i..n);
8+
9+
arr.swap(i, pos);
10+
}
11+
}
12+
13+
fn num_collisions(diags: &[usize], diag: usize, diag_2: usize) -> usize {
14+
diags[diag] - 1 + if diag == diag_2 { 0 } else { diags[diag_2] - 1 }
15+
}
16+
17+
fn num_collisions_new(diags: &[usize], diag: usize, diag_2: usize) -> usize {
18+
diags[diag] + if diag == diag_2 { 1 } else { diags[diag_2] }
19+
}
20+
21+
/**
22+
* Solution is based on [paper](https://citeseerx.ist.psu.edu/viewdoc/download;jsessionid=4DC9292839FE7B1AFABA1EDB8183242C?doi=10.1.1.57.4685&rep=rep1&type=pdf)
23+
*/
24+
pub fn solve_n_queens(n: usize, mandatory_coords: (usize, usize)) -> Option<String> {
25+
let (mandatory_col, mandatory_row) = mandatory_coords;
26+
27+
// pick a random number of iterations to validate different permutations
28+
for _ in 0..(if n > 20 { 10 } else { 100 }) {
29+
let mut permutation: Vec<_> = (0..n).collect();
30+
let mut main_diags = vec![0usize; 2 * n - 1];
31+
let mut second_diags = vec![0usize; 2 * n - 1];
32+
33+
shuffle(&mut permutation);
34+
35+
let pos = permutation
36+
.iter()
37+
.position(|&x| x == mandatory_col)
38+
.unwrap();
39+
40+
permutation.swap(pos, mandatory_row);
41+
42+
let info = |row: usize, col: usize| (n + col - row - 1, row + col);
43+
44+
for (row, col) in permutation.iter().enumerate() {
45+
let (main_diag, second_diag) = info(row, *col);
46+
47+
main_diags[main_diag] += 1;
48+
second_diags[second_diag] += 1;
49+
}
50+
51+
let mut has_swapped = true;
52+
53+
while has_swapped {
54+
has_swapped = false;
55+
56+
for row in 0..n {
57+
let col = permutation[row];
58+
let (main_diag, second_diag) = info(row, col);
59+
60+
if row == mandatory_row {
61+
continue;
62+
}
63+
64+
for row_2 in row + 1..n {
65+
if row_2 == mandatory_row {
66+
continue;
67+
}
68+
69+
let col_2 = permutation[row_2];
70+
let (main_diag_2, second_diag_2) = info(row_2, col_2);
71+
let (main_diag_new, second_diag_new) = info(row, col_2);
72+
let (main_diag_2_new, second_diag_2_new) = info(row_2, col);
73+
74+
if num_collisions(&main_diags, main_diag, main_diag_2)
75+
+ num_collisions(&second_diags, second_diag, second_diag_2)
76+
> num_collisions_new(&main_diags, main_diag_new, main_diag_2_new)
77+
+ num_collisions_new(&second_diags, second_diag_new, second_diag_2_new)
78+
{
79+
permutation[row] = col_2;
80+
permutation[row_2] = col;
81+
main_diags[main_diag] -= 1;
82+
main_diags[main_diag_new] += 1;
83+
second_diags[second_diag] -= 1;
84+
second_diags[second_diag_new] += 1;
85+
main_diags[main_diag_2] -= 1;
86+
main_diags[main_diag_2_new] += 1;
87+
second_diags[second_diag_2] -= 1;
88+
second_diags[second_diag_2_new] += 1;
89+
has_swapped = true;
90+
break;
91+
}
92+
}
93+
}
94+
}
95+
96+
if main_diags.iter().chain(second_diags.iter()).all(|&x| x < 2) {
97+
let mut result: Vec<String> = Vec::with_capacity(n);
98+
99+
for row in 0..n {
100+
let mut row_repr = vec!['.'; n];
101+
102+
row_repr[permutation[row]] = 'Q';
103+
row_repr.push('\n');
104+
105+
result.push(row_repr.into_iter().collect());
106+
}
107+
108+
return Some(result.into_iter().collect());
109+
}
110+
}
111+
112+
None
113+
}
114+
115+
#[cfg(test)]
116+
mod tests {
117+
use super::solve_n_queens;
118+
119+
#[test]
120+
fn basic_tests() {
121+
let basic_tests = vec![(1, (0, 0)), (4, (2, 0)), (8, (3, 0))];
122+
for (n, fixed) in basic_tests.into_iter() {
123+
test_solution(n, fixed);
124+
}
125+
}
126+
127+
#[test]
128+
fn no_solution_tests() {
129+
let no_solutions = vec![(2, (0, 0)), (3, (2, 0)), (6, (0, 0))];
130+
for (n, fixed) in no_solutions.into_iter() {
131+
test_no_solution(n, fixed);
132+
}
133+
}
134+
135+
#[test]
136+
fn big_board() {
137+
let basic_tests = vec![(20, (3, 0))];
138+
for (n, fixed) in basic_tests.into_iter() {
139+
test_solution(n, fixed);
140+
}
141+
}
142+
143+
fn check_board(board: &[u8], n: usize, fixed: (usize, usize)) {
144+
let mut offset = 0;
145+
let mut num_queens = 0;
146+
let mut queens: Vec<Option<usize>> = vec![None; n];
147+
#[allow(clippy::needless_range_loop)] // should be more clear to keep the `y` indexing
148+
for y in 0..n {
149+
for x in 0..n {
150+
match board[offset] {
151+
b'Q' => {
152+
assert!(
153+
queens[y].is_none(),
154+
"The board should not have horizontal attacks between Queens"
155+
);
156+
num_queens += 1;
157+
queens[y] = Some(x);
158+
}
159+
b'.' => {}
160+
_ => panic!("The board has invalid character"),
161+
}
162+
offset += 1;
163+
}
164+
165+
assert_eq!(
166+
board[offset], b'\n',
167+
"The board has missing/incorrect characters"
168+
);
169+
offset += 1;
170+
}
171+
172+
assert_eq!(
173+
num_queens, n,
174+
"The number of queens should be equal to size"
175+
);
176+
177+
let queens = queens.into_iter().map(Option::unwrap).collect::<Vec<_>>();
178+
assert!(
179+
queens[fixed.1] == fixed.0,
180+
"The mandatory queen is not in the required position"
181+
);
182+
183+
// Check no attacks
184+
let mut taken_cols = vec![false; n];
185+
let mut taken_diag1 = vec![false; 2 * n];
186+
let mut taken_diag2 = vec![false; 2 * n];
187+
for row in 0..n {
188+
let col = queens[row];
189+
assert!(
190+
!taken_cols[col],
191+
"The board has vertical attacks between Queens"
192+
);
193+
assert!(
194+
!taken_diag1[col + row],
195+
"The board has diag1 attacks between Queens"
196+
);
197+
assert!(
198+
!taken_diag2[n + col - row - 1],
199+
"The board has diag2 attacks between Queens"
200+
);
201+
taken_cols[col] = true;
202+
taken_diag1[col + row] = true;
203+
taken_diag2[n + col - row - 1] = true;
204+
}
205+
}
206+
207+
fn test_solution(n: usize, fixed: (usize, usize)) {
208+
if let Some(board) = solve_n_queens(n, fixed) {
209+
check_board(&board.as_bytes(), n, fixed);
210+
} else {
211+
panic!("Returned None when there's a solution");
212+
}
213+
}
214+
215+
fn test_no_solution(n: usize, fixed: (usize, usize)) {
216+
assert_eq!(
217+
solve_n_queens(n, fixed),
218+
None,
219+
"Expected None when no solution is possible"
220+
);
221+
}
222+
}

codewars/n-queens-problem/src/main.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use n_queens_problem::solve_n_queens;
2+
3+
pub fn main() {
4+
let solution = solve_n_queens(8, (3, 0)).unwrap_or("None".to_string());
5+
6+
println!("{solution}")
7+
}

0 commit comments

Comments
 (0)