sudoku/
sol.rs

1//! Items for solving a sudoku.
2//!
3//! The general algorithm is based on
4//! [that proposed by Daniel Beer](https://dlbeer.co.nz/articles/sudoku.html).
5//!
6//! # Approach
7//!
8//! We first find the empty element with the fewest possible values. We then try each of these
9//! candidates, recursively solving from there until we (hopefully) find a solution.
10//!
11//! We search until we've either found two solutions (meaning that the puzzle is not uniquely
12//! solvable) or exhausted the search tree.
13//!
14//! # Scoring
15//! During the solving process, we calculate a *branch-difficulty score*, `S = Σ((B - 1)²)`, where
16//! `B` is the branching factor at a given node in the search tree from root to solution.
17//!
18//! If no backtracking occurs, the branch-difficulty score is 0.
19//!
20//! ## Tabulation
21//! The final difficulty score is given by `D = S * C + E`, where `C` is the first power of 10
22//! greater than the number of elements and `E` is the number of empty elements.
23use sudoku::Grid;
24use Element;
25use Point;
26use Sudoku;
27use DIMENSIONS;
28
29use std::ops::{Index, IndexMut};
30
31/// Represents the difficulty of a puzzle.
32#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
33pub enum Difficulty {
34    #[doc(hidden)]
35    /// Filler
36    Unplayable,
37    /// Very easy puzzles, ideal for learning a new game.
38    Beginner,
39    /// More advanced puzzles, good for practicing a new game.
40    Easy,
41    /// Intermediate puzzles, good for a casual puzzle-solving session.
42    Intermediate,
43    /// Advanced, thought-provoking puzzles.
44    Difficult,
45    /// Coffee shop puzzles.
46    Advanced,
47}
48
49impl From<usize> for Difficulty {
50    fn from(score: usize) -> Self {
51        use Difficulty::*;
52        match score {
53            0...49 => Unplayable,
54            50...150 => Beginner,
55            151...250 => Easy,
56            251...400 => Intermediate,
57            401...550 => Difficult,
58            _ => Advanced,
59        }
60    }
61}
62
63/// Encodes errors encountered while attempting a puzzle solution.
64#[derive(Clone, Debug)]
65#[allow(missing_copy_implementations)] // This is an error type.
66pub enum Error {
67    /// A mere placeholder; this will be replaced by proper errors in a future revision.
68    Unknown,
69    #[doc(hidden)]
70    __TestOther,
71}
72
73/// Trait defining a solvable puzzle.
74pub trait Solve: Sized {
75    /// Returns the puzzle's unique solution if it exists.
76    fn solution(&self) -> Result<Self, Error>;
77    /// Whether the puzzle has a unique solution.
78    fn is_uniquely_solvable(&self) -> bool {
79        self.solution().is_ok()
80    }
81}
82
83/// Trait defining a puzzle with quantifiable difficulty.
84pub trait Score: Solve {
85    /// The raw difficulty score of this puzzle.
86    fn score(&self) -> Option<usize>;
87    /// The graded difficulty score of this puzzle.
88    fn difficulty(&self) -> Option<Difficulty> {
89        self.score().map(|s| s.into())
90    }
91}
92
93// TODO(#12): Allow higher orders (u128?)
94#[derive(Clone, Copy, Debug, PartialEq)]
95pub struct PossibilitySet {
96    pub values: u64,
97}
98
99impl PossibilitySet {
100    /// Creates a new set full of possibilities.
101    pub fn new(order: u8) -> Self {
102        let mut values = 0;
103        for i in 1..=order.pow(2) as usize {
104            values |= 1 << (i - 1);
105        }
106        Self { values }
107    }
108    /// Elminates the given possible value from the set and returns the result.
109    pub fn eliminate(&self, value: usize) -> Option<Self> {
110        let values = self.values & !(1 << (value - 1));
111        match values {
112            0 => None,
113            _ => Some(Self { values }),
114        }
115    }
116    /// The number of possible values in this set.
117    pub fn freedom(&self) -> usize {
118        let mut x = self.values;
119        let mut n = 0;
120        while x > 0 {
121            x &= x - 1;
122            n += 1;
123        }
124        n
125    }
126    /// Whether the set contains the given possibility.
127    pub fn contains(&self, value: usize) -> bool {
128        self.values | (1 << (value - 1)) == self.values
129    }
130}
131
132#[derive(Debug)]
133pub struct PossibilityMap {
134    possibilities: Vec<Option<PossibilitySet>>,
135    order: u8,
136}
137
138impl PossibilityMap {
139    /// Constructs a blank possibilitiy map of the given order.
140    pub fn new(order: u8) -> Self {
141        Self {
142            possibilities: vec![
143                Some(PossibilitySet::new(order));
144                (order as usize).pow(2 + DIMENSIONS as u32)
145            ],
146            order,
147        }
148    }
149
150    /// Removes the given value from the set of possibilities at the given location.
151    // There's no way it's cheaper to reconstruct the map each time, so we make this mutating.
152    // TODO(#10): Benchmark
153    pub fn eliminate(&mut self, index: Point, value: usize) {
154        self[index] = self[index].and_then(|e| e.eliminate(value));
155    }
156
157    // Returns the next easiest index to solve and its corresponding value.
158    pub fn next(&self) -> (Option<Point>, Option<PossibilitySet>) {
159        let mut best = None;
160        let mut best_index = None;
161        let mut best_score = None;
162        for index in self.points() {
163            if let Some(element) = self[index] {
164                if best_score.is_none() || best_score.unwrap() > element.freedom() {
165                    best = Some(element);
166                    best_index = Some(index);
167                    best_score = Some(element.freedom());
168                }
169            }
170        }
171        (best_index, best)
172    }
173}
174
175impl Index<Point> for PossibilityMap {
176    type Output = Option<PossibilitySet>;
177
178    fn index(&self, index: Point) -> &Self::Output {
179        let index = index.fold(self.order);
180        &self.possibilities[index]
181    }
182}
183
184impl IndexMut<Point> for PossibilityMap {
185    fn index_mut(&mut self, index: Point) -> &mut Option<PossibilitySet> {
186        let index = index.fold(self.order);
187        &mut self.possibilities[index]
188    }
189}
190
191impl Grid for PossibilityMap {
192    fn points(&self) -> Vec<Point> {
193        (0..(self.order as usize).pow(2 + DIMENSIONS as u32))
194            .map(|p| Point::unfold(p, self.order))
195            .collect()
196    }
197}
198
199impl From<Sudoku> for PossibilityMap {
200    fn from(sudoku: Sudoku) -> Self {
201        let order = sudoku.order;
202        let mut map = PossibilityMap::new(order);
203        for i in 0..(sudoku.order as usize).pow(2 + DIMENSIONS as u32) {
204            let point = Point::unfold(i, order);
205            if sudoku[point].is_some() {
206                map[point] = None;
207            } else {
208                let groups = sudoku.groups(point);
209                for group in groups.iter() {
210                    let elements = group.elements();
211                    for element in elements {
212                        match element {
213                            Some(Element(value)) => {
214                                map.eliminate(point, value as usize);
215                            }
216                            None => {}
217                        }
218                    }
219                }
220            }
221        }
222        map
223    }
224}
225
226pub fn solve(puzzle: &Sudoku) -> Result<Sudoku, Error> {
227    solve_and_score(puzzle).map(|(sol, _)| sol)
228}
229
230pub fn solve_and_score(puzzle: &Sudoku) -> Result<(Sudoku, usize), Error> {
231    let mut context = Context {
232        problem: puzzle.clone(),
233        count: 0,
234        solution: None,
235        branch_score: 0,
236    };
237    recurse(&mut context, 0);
238    let s = context.branch_score;
239    let c = calculate_c(puzzle) as isize;
240    let e = count_empty(puzzle) as isize;
241    context
242        .solution
243        .ok_or(Error::Unknown)
244        .map(|sol| (sol, (s * c + e) as usize))
245}
246
247struct Context {
248    problem: Sudoku,
249    count: usize,
250    solution: Option<Sudoku>,
251    branch_score: isize,
252}
253
254fn recurse(mut context: &mut Context, difficulty: isize) {
255    let problem = context.problem.clone();
256    let map: PossibilityMap = problem.into();
257    match map.next() {
258        (None, _) => {
259            if context.problem.is_complete() {
260                // We're done! Stash the solution and return.
261                if context.count == 0 {
262                    context.branch_score = difficulty;
263                    context.solution = Some(context.problem.clone());
264                }
265                context.count += 1;
266            }
267            return;
268        }
269        (Some(index), Some(set)) => {
270            let branch_factor = set.freedom() as isize - 1;
271            let possible = (1..=(context.problem.order as usize).pow(2))
272                .filter(|v| set.contains(*v))
273                .collect::<Vec<_>>();
274            let difficulty = difficulty + branch_factor.pow(DIMENSIONS as u32);
275            for value in possible {
276                let problem = context
277                    .problem
278                    .substitute(index, Some(Element(value as u8)));
279                context.problem = problem;
280                recurse(&mut context, difficulty);
281                if context.count > 1 {
282                    // There are multiple solutions; abort.
283                    return;
284                }
285            }
286            context.problem = context.problem.substitute(index, None);
287        }
288        _ => unreachable!(),
289    }
290}
291
292/// Returns the number of empty cells in the passed sudoku.
293///
294/// Useful for scoring difficulty (see [Scoring](#Scoring)).
295fn count_empty(sudoku: &Sudoku) -> usize {
296    sudoku
297        .elements
298        .iter()
299        .filter(|e| e.is_none())
300        .collect::<Vec<_>>()
301        .len()
302}
303
304/// Calculates the value of `C`, as discussed in [Scoring](#Scoring).
305fn calculate_c(sudoku: &Sudoku) -> usize {
306    let order = sudoku.order;
307    10.0_f64.powf((order as f64).powf(4.0).log10().ceil()) as usize
308}
309
310/// Scores the passed, if it's solvable.
311pub fn score(sudoku: &Sudoku) -> Option<usize> {
312    solve_and_score(&sudoku).ok().map(|(_, s)| s)
313}
314
315#[cfg(test)]
316mod tests {
317
318    use sol::{calculate_c, Error, PossibilityMap, PossibilitySet, Solve};
319    use Point;
320    use Sudoku;
321    use DIMENSIONS;
322
323    struct DummyPuzzle(bool);
324
325    impl DummyPuzzle {
326        fn new(solvable: bool) -> Self {
327            Self { 0: solvable }
328        }
329    }
330
331    impl Solve for DummyPuzzle {
332        fn solution(&self) -> Result<Self, Error> {
333            if self.0 {
334                Ok(Self { 0: true })
335            } else {
336                Err(Error::__TestOther)
337            }
338        }
339    }
340
341    #[test]
342    fn test_is_uniquely_solvable() {
343        let solvable = DummyPuzzle::new(true);
344        assert_eq!(solvable.is_uniquely_solvable(), true);
345        let unsolvable = DummyPuzzle::new(false);
346        assert_eq!(unsolvable.is_uniquely_solvable(), false);
347    }
348
349    #[test]
350    fn test_calculate_c() {
351        let sudoku = Sudoku::new(3);
352        assert_eq!(calculate_c(&sudoku), 100);
353        let sudoku = Sudoku::new(4);
354        assert_eq!(calculate_c(&sudoku), 1_000);
355        let sudoku = Sudoku::new(5);
356        assert_eq!(calculate_c(&sudoku), 1_000);
357        let sudoku = Sudoku::new(6);
358        assert_eq!(calculate_c(&sudoku), 10_000);
359    }
360
361    #[test]
362    fn test_map_new() {
363        for order in 1..6 {
364            let map = PossibilityMap::new(order);
365            for i in 0..(order as usize).pow(DIMENSIONS as u32 + 2) {
366                let index = Point::unfold(i, order);
367                let set = PossibilitySet::new(order);
368                assert_eq!(map[index], Some(set));
369            }
370        }
371    }
372
373    #[test]
374    fn test_map_from_sudoku() {
375        // TODO(#11): More cases
376        let sudoku = Sudoku::new(3);
377        let map: PossibilityMap = sudoku.into();
378        for p in map.possibilities {
379            assert_eq!(p, Some(PossibilitySet::new(3)));
380        }
381    }
382
383    #[test]
384    fn test_set_new() {
385        let set = PossibilitySet::new(3);
386        for i in 1..10 {
387            assert!(set.contains(i));
388        }
389    }
390
391    #[test]
392    fn test_set_eliminate() {
393        let mut set = PossibilitySet::new(3);
394        for i in 1..9 {
395            set = set.eliminate(i).unwrap();
396            assert!(!set.contains(i));
397        }
398        assert_eq!(set.eliminate(9), None);
399    }
400
401    #[test]
402    fn test_set_freedom() {
403        let mut set = PossibilitySet::new(3);
404        for i in 1..9 {
405            set = set.eliminate(i).unwrap();
406            assert_eq!(set.freedom(), 9 - i);
407        }
408    }
409}