Browse Source

Initial work on solve_logic (AnySolver).

Steve Thielemann 4 months ago
parent
commit
4595eb3b37
2 changed files with 160 additions and 105 deletions
  1. 35 35
      sudoku/src/ksudoku.rs
  2. 125 70
      sudoku/src/sudoku.rs

+ 35 - 35
sudoku/src/ksudoku.rs

@@ -33,7 +33,7 @@ What I want from load_ksudoku:
 
 I get values (the puzzle), I also need solution.
 
-Having order will allow me to use AnyBoard::new(order) and load it up.  
+Having order will allow me to use AnyBoard::new(order) and load it up.
 
 Currently, it loads, and will fail if it isn't Plain, sudoku, 9.
 I'm not sure what future ksudoku puzzles I want to support, so I'd like to
@@ -42,11 +42,11 @@ have that as flexible as possible.
 save_ksudoku, would need to save those fields as well...
  */
 
- #[derive(Debug)]
- pub struct Ksudoku {
+#[derive(Debug)]
+pub struct Ksudoku {
     /// puzzle string:
     /// _ is blank, starts with 'b' (for 1), 'c' for 2.
-    pub puzzle :String,
+    pub puzzle: String,
     /// This contains the solution to the puzzle.
     pub solution: String,
     /// "Plain"
@@ -55,9 +55,9 @@ save_ksudoku, would need to save those fields as well...
     pub puzzle_type: String,
     /// Puzzle width (9, 16, 25).
     pub order: u8,
- }
+}
 
- impl Default for Ksudoku {
+impl Default for Ksudoku {
     fn default() -> Self {
         Ksudoku {
             puzzle: String::new(),
@@ -69,14 +69,14 @@ save_ksudoku, would need to save those fields as well...
     }
 }
 
- /// Load ksudoku file, returning Ksudoku structure.
- /// - It is up to the caller to determine if they can handle the types
- ///   and size (order).
+/// Load ksudoku file, returning Ksudoku structure.
+/// - It is up to the caller to determine if they can handle the types
+///   and size (order).
 pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<Ksudoku, Box<dyn error::Error>> {
     let fh = File::open(filename)?;
     let buffer = BufReader::new(fh);
     let parser = EventReader::new(buffer);
-    let mut element_name: String = String::new();    
+    let mut element_name: String = String::new();
     let mut details = Ksudoku::default();
 
     for e in parser {
@@ -98,9 +98,12 @@ pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<Ksudoku, Box<dyn err
 
                     let blank = String::new();
 
-                    details.specific_type = attrs.get(&"specific-type".to_string()).unwrap_or(&blank).clone();
+                    details.specific_type = attrs
+                        .get(&"specific-type".to_string())
+                        .unwrap_or(&blank)
+                        .clone();
                     details.puzzle_type = attrs.get(&"type".to_string()).unwrap_or(&blank).clone();
-                    let order  = attrs.get(&"order".to_string()).unwrap_or(&blank).clone();
+                    let order = attrs.get(&"order".to_string()).unwrap_or(&blank).clone();
 
                     if !order.is_empty() {
                         let r = order.parse::<u8>();
@@ -155,7 +158,7 @@ pub fn save_ksudoku(
     event = WrXmlEvent::start_element("graph")
         .attr("specific-type", &data.specific_type)
         .attr("type", &data.puzzle_type)
-        .attr("order", &order )
+        .attr("order", &order)
         .into();
     writer.write(event)?;
     event = WrXmlEvent::end_element().into();
@@ -197,12 +200,11 @@ mod tests {
     use std::path::PathBuf;
 
     fn test_files_path() -> PathBuf {
-        
-    let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
-    d.push("test_files");
+        let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+        d.push("test_files");
         d
     }
-    
+
     #[test]
     fn load_puzzles() {
         let mut testfile = test_files_path();
@@ -218,21 +220,21 @@ mod tests {
         let mut board = AnyBoard::new(size);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         assert!(load.is_ok());
-        
+
         // Ok, puzzle is loaded!
         let strings = board.to_strings();
         assert_eq!(
             strings,
             vec![
                 "  4   8  ",
-" 6  1  2 ",
-"3  9 5  6",
-"  72 16  ",
-" 3     9 ",
-"  18 35  ",
-"9  3 6  2",
-" 4  5  8 ",
-"  3   7  ",
+                " 6  1  2 ",
+                "3  9 5  6",
+                "  72 16  ",
+                " 3     9 ",
+                "  18 35  ",
+                "9  3 6  2",
+                " 4  5  8 ",
+                "  3   7  ",
             ]
         );
 
@@ -249,7 +251,7 @@ mod tests {
         let mut board = AnyBoard::new(size);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         assert!(load.is_ok());
-        
+
         // Ok, puzzle is loaded!
         let strings: Vec<String> = board.to_strings();
         assert_eq!(
@@ -270,11 +272,11 @@ mod tests {
                 "OPC  N H   F J  ",
                 "  EHJ I   MA  CK",
                 "     FM   IPA  D",
-                "FM  L    H    P "                
+                "FM  L    H    P "
             ]
         );
         // board.display();
-        
+
         let mut testfile = test_files_path();
         testfile.push("25-puzzle");
 
@@ -288,12 +290,12 @@ mod tests {
         let mut board = AnyBoard::new(size);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         assert!(load.is_ok());
-        
+
         // Ok, puzzle is loaded!
         // board.display();
         let strings: Vec<String> = board.to_strings();
         assert_eq!(
-            strings, 
+            strings,
             vec![
                 " T DPF    Y H R    WSX L ",
                 "L QF     J X C G     UB D",
@@ -320,9 +322,7 @@ mod tests {
                 " FR H DQOC  K  INBW T UY ",
                 "T JL     G I P F     OC W",
                 " B KMV    Q J H    GRI E "
-        
             ]
-        );        
-
+        );
     }
-}
+}

+ 125 - 70
sudoku/src/sudoku.rs

@@ -445,11 +445,13 @@ impl AnyBoard {
         solutions.reserve(max as usize);
         workset.brute_force(&mut total_solutions, &mut solutions, &groups);
 
+        /* 
         if solutions.len() > 0 {
             println!("*** A Solution:");
             solutions[0].display();
             println!("***");
         }
+        */
         (total_solutions, solutions)
     }
 
@@ -711,6 +713,7 @@ impl AnySolver {
     /// Validate the board
     /// Reuse reset_possible code, verify the values are possible.
     /// - This does not check if the board is completed.
+    /// - It does check that blanks have possible values.
     pub fn validate_board(&mut self) -> bool {
         let mut has_blanks = false;
 
@@ -724,9 +727,9 @@ impl AnySolver {
                         // I was going to reset_possible, but the board is broken!
                         // Leave in a broken state.
 
-                        // println!("Width: {}, value: {}", self.board.width, value);
-                        println!("Invalid at ({},{}) can't place {}.", x + 1, y + 1, value);
-                        self.board.display();
+                        // log (maybe)
+                        // println!("Invalid at ({},{}) can't place {}.", x + 1, y + 1, value);
+                        // self.board.display();
                         return false;
                     }
                     self.process_move(x, y, value);
@@ -746,8 +749,9 @@ impl AnySolver {
                     if value == 0 {
                         let count = self.possible.possible[self.possible.pos(x, y)].count_set();
                         if count == 0 {
-                            println!("Invalid ({},{}) = no values possible.", x + 1, y + 1);
-                            self.board.display();
+                            // log (maybe)
+                            // println!("Invalid ({},{}) = no values possible.", x + 1, y + 1);
+                            // self.board.display();
                             return false;
                         }
                     }
@@ -858,81 +862,92 @@ impl AnySolver {
         true
     }
 
-    // This seems .. broken.  Not sure.  4x4 kicked me.
-    pub fn brute_force_solver(&mut self) -> u16 {
-        let mut workset = self.clone();
-        workset.reset_possible();
-
-        let mut total_solutions: u16 = 0;
-        let mut solutions: Vec<AnyBoard> = Vec::new();
-        solutions.reserve(1);
-
-        workset.brute_force(&mut total_solutions, &mut solutions);
+    /// Solve by logic alone.
+    /// - Returns true when something was added to board.
+    /// - Call solve until it returns false.
+    /// - It might not be solved (if guessing is required).
+    pub fn solve_logic(&mut self) -> bool {
+        // Pass 1: Look for singles in the possible sets.
+        let mut found_something = false;
 
-        if solutions.len() > 0 {
-            println!("*** A solution:");
-            solutions[0].display();
-            println!("***");
+        for i in 0 .. self.possible.max_index {
+            if self.board.board[i] != 0 {
+                // Skip, if position already filled.
+                continue;
+            }
+            if self.possible.possible[i].count_set() == 1 {
+                // Get value
+                let value = self.possible.possible[i].iter().next().unwrap();
+                let pos = self.board.xy(i);
+                self.set(pos.0, pos.1, value);
+                found_something = true;
+            }
         }
 
-        total_solutions
-    }
-
-    fn brute_force(&mut self, total_solutions: &mut u16, solutions: &mut Vec<AnyBoard>) -> bool {
-        // Verify that possible is setup correctly.
-        // self.validate_board(); // The better choice here.
-        // self.reset_possible();
+        let width = self.group.width;
+        let grp = self.group.clone();
+        let mut values = Bits(0); // HashSet<u8> = HashSet::new();
 
-        for idx in 0..self.board.max_index {
-            if self.board.board[idx] == 0 {
-                // Blank found
-                let (x, y) = self.board.xy(idx);
-                if DEBUG_OUTPUT {
-                    println!("idx={} ({},{})", idx, x + 1, y + 1);
-                    self.board.display();
-                    self.possible.display();
+        let mut group_process = |this: &mut Self, grp: &[usize]| {
+            // Collect all the possible values within the group.
+            values.clear();
+            for gidx in 0..width {
+                // println!("possible: {:?}", this.possible[grp.items[gidx as usize] as usize]);
+                for v in this.possible.possible[grp[gidx as usize]].iter() {
+                    values.set(v, true);
                 }
+                // values.extend(this.possible[grp.0[gidx as usize] as usize]);
+                // println!("now     : {:?}", this.possible[grp.items[gidx as usize] as usize]);
+            }
 
-                // save possible
-                let old_possible = self.possible.clone();
-
-                for value in 1..=self.board.width {
-                    if self.possible.get(idx, value as usize) {
-                        // Ok, it could go here.
-                        if DEBUG_OUTPUT {
-                            println!("({},{})={}", x + 1, y + 1, value);
-                        }
-                        self.set(x, y, value);
-                        self.board.display();
+            // println!("values {:?}", values);
 
-                        if self.board.complete() {
-                            if *total_solutions < solutions.capacity() as u16 {
-                                solutions.push(self.board.clone());
-                            }
-                            *total_solutions += 1;
+            // Now, check for singles.
+            for v in values.iter() {
+                let mut count = 0;
+                let mut pos = 0;
+                for gidx in 0..width{
+                    if this.possible.possible[grp[gidx as usize] ].get(v as usize) {
+                        // if this.possible[grp.0[gidx as usize] as usize].contains(&v) {
+                        count += 1;
+                        pos = grp[gidx as usize];
+                        if count > 1 {
                             break;
-                        } else {
-                            if self.brute_force(total_solutions, solutions) {
-                                return true;
-                            }
                         }
-                        // Restore
-                        self.possible.copy(&old_possible);
                     }
                 }
-                if DEBUG_OUTPUT {
-                    println!("Rewind!");
-                }
-                self.set(x, y, 0);
-                // self.possible.copy(&old_possible);
-                if DEBUG_OUTPUT {
-                    self.board.display();
-                    self.possible.display();
+                if count == 1 {
+                    // don't need this, it was v!
+                    // let value = this.possible[pos as usize].iter().next().cloned().unwrap();
+                    /*
+                    if debug {
+                        println!("Set2 {:?} to {}", xy(pos), v);
+                    }
+                    */
+                    let xy = this.board.xy(pos);
+                    this.set(xy.0, xy.1, v);
+                    found_something = true;
                 }
-                return false;
             }
+        };
+
+        // Change to 0..WIDTH ...  Keep it simple.
+        for i in 0..width {
+            let mut g = grp.column(i);
+            group_process(self, g);
+            g = grp.row(i);
+            group_process(self, g);
+            g = grp.cell(i);
+            group_process(self, g);
         }
-        false
+
+        if found_something {
+            return found_something;
+        }
+
+        // Pair processing.
+
+        found_something
     }
 }
 
@@ -992,7 +1007,7 @@ mod tests {
                 "OPC  N H   F J  ",
                 "  EHJ I   MA  CK",
                 "     FM   IPA  D",
-                "FM  L    H    P "                
+                "FM  L    H    P "
             ]
         );
         // board.display();
@@ -1006,7 +1021,7 @@ mod tests {
         // board.display();
         let strings: Vec<String> = board.to_strings();
         assert_eq!(
-            strings, 
+            strings,
             vec![
                 " T DPF    Y H R    WSX L ",
                 "L QF     J X C G     UB D",
@@ -1033,7 +1048,6 @@ mod tests {
                 " FR H DQOC  K  INBW T UY ",
                 "T JL     G I P F     OC W",
                 " B KMV    Q J H    GRI E "
-        
             ]
         );
         let mut solver = AnySolver::new_from(&board);
@@ -1053,11 +1067,42 @@ mod tests {
         assert_eq!(9, board.width);
         assert_eq!(81, board.max_index);
 
-        board.display();
+        // board.display();
+        let strings: Vec<String> = board.to_strings();
+        assert_eq!(
+            strings,
+            vec![
+                "  17 83  ",
+                " 73   68 ",
+                "2  9 4  1",
+                "   1 7   ",
+                " 2     9 ",
+                " 14   86 ",
+                "  83 19  ",
+                "         ",
+                "4 2   5 6"
+            ]
+        );
+
         let mut solver = AnySolver::new_from(&board);
         assert!(solver.validate_board());
         let solutions = solver.board.brute_force_solver(2);
         assert!(solutions.0 == 1, "Expected 1 solution, got {}", solutions.0);
+        let strings: Vec<String> = solutions.1[0].to_strings();
+        assert_eq!(
+            strings,
+            vec![
+                "541768329",
+                "973512684",
+                "286934751",
+                "869157243",
+                "325486197",
+                "714293865",
+                "658341972",
+                "197625438",
+                "432879516",
+            ]
+        );
 
         // 4x4 board takes 40 minutes
 
@@ -1093,6 +1138,7 @@ mod tests {
         let mut solver = AnySolver::new(3);
         solver.make();
         solver.board.display();
+        assert!(solver.validate_board());
     }
 
     #[test]
@@ -1281,6 +1327,15 @@ impl<'a> BoardPossible<'a> {
 }
 */
 
+/*
+  ___  _     _    ____          _      
+ / _ \| | __| |  / ___|___   __| | ___ 
+| | | | |/ _` | | |   / _ \ / _` |/ _ \
+| |_| | | (_| | | |__| (_) | (_| |  __/
+ \___/|_|\__,_|  \____\___/ \__,_|\___|
+
+ */
+
 #[derive(Debug, Clone, Copy)]
 pub struct Sudoku {
     pub board: [u8; MAX_SIZE as usize],      // Board