1use xcov::{DlxBuilder, ExactCoverProblem, MrvExactCoverSearch};
14
15#[must_use]
19pub fn solve(puzzle: &str) -> Option<String> {
20 if puzzle.len() != 81 {
21 return None;
22 }
23
24 let mut builder = DlxBuilder::new(9 * 9 * 4, 0);
25 let mut givens = IntSet::<{ (324 + 63) / 64 }>::new();
26
27 for (i, ch) in puzzle.chars().enumerate() {
29 let Some(digit) = ch.to_digit(10) else {
30 continue;
31 };
32 if !(1..=9).contains(&digit) {
33 continue;
34 }
35 let digit = (digit - 1) as usize;
36 let option = sudoku_items(i, digit);
37 builder.add_option(&option);
38 for &item in &option {
39 givens.insert(item);
40 }
41 }
42
43 for cell in 0..81 {
45 for digit in 0..9 {
46 let option = sudoku_items(cell, digit);
47 if option.iter().any(|&i| givens.get(i)) {
48 continue;
49 }
50 builder.add_option(&option);
51 }
52 }
53
54 let mut ec = MrvExactCoverSearch::new(builder.build());
55 ec.search();
56
57 let solution = ec.current_solution()?;
58 let mut result = vec![b' '; 81];
59 const DIGIT_BYTES: &[u8] = b"123456789";
60 for option in solution {
61 let Ok(option): Result<[usize; 4], _> = option.try_into() else {
62 unreachable!()
63 };
64 let [cell, digit] = sudoku_invert_items(&option);
65 result[cell] = DIGIT_BYTES[digit];
66 }
67 Some(String::from_utf8(result).expect("valid utf8 that we constructed ourselves"))
68}
69
70fn sudoku_items(cell: usize, digit: usize) -> [usize; 4] {
72 let row = cell / 9;
73 let col = cell % 9;
74 [
75 1 + cell,
76 1 + 81 + row * 9 + digit,
77 1 + 81 * 2 + col * 9 + digit,
78 1 + 81 * 3 + (row / 3 * 3 + col / 3) * 9 + digit,
79 ]
80}
81
82fn sudoku_invert_items(items: &[usize; 4]) -> [usize; 2] {
84 let digit = (items[1] - 81 - 1) % 9;
85 [items[0] - 1, digit]
86}
87
88struct IntSet<const N: usize> {
90 bits: [u64; N],
91}
92
93impl<const N: usize> IntSet<N> {
94 fn new() -> Self {
95 Self { bits: [0; N] }
96 }
97
98 fn coords(n: usize) -> (usize, u32) {
99 let i = n / 64;
100 assert!(i < N);
101 let sh = (n % 64).try_into().expect("mod 64 fits in u32");
102 (i, sh)
103 }
104
105 fn insert(&mut self, n: usize) {
106 let (i, sh) = Self::coords(n);
107 self.bits[i] |= 1 << sh;
108 }
109
110 fn get(&self, n: usize) -> bool {
111 let (i, sh) = Self::coords(n);
112 self.bits[i] & 1 << sh != 0
113 }
114}
115
116fn main() {
117 let Some(puzzle) = std::env::args().nth(1) else {
118 panic!("Missing puzzle argument")
119 };
120 if let Some(solution) = solve(&puzzle) {
121 println!("{solution}");
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn puzzle1() {
131 let solution = solve(
132 "769000028000400009000000005005000000090860070280003000008300091002080600000000200",
133 )
134 .unwrap();
135 let expected =
136 "769531428521478369834296715175942836493865172286713954648327591352189647917654283";
137 assert_eq!(solution, expected);
138 }
139}