Browse Source

Initial work on solve_logic (AnySolver).

Steve Thielemann 3 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.
 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.
 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
 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...
 save_ksudoku, would need to save those fields as well...
  */
  */
 
 
- #[derive(Debug)]
- pub struct Ksudoku {
+#[derive(Debug)]
+pub struct Ksudoku {
     /// puzzle string:
     /// puzzle string:
     /// _ is blank, starts with 'b' (for 1), 'c' for 2.
     /// _ is blank, starts with 'b' (for 1), 'c' for 2.
-    pub puzzle :String,
+    pub puzzle: String,
     /// This contains the solution to the puzzle.
     /// This contains the solution to the puzzle.
     pub solution: String,
     pub solution: String,
     /// "Plain"
     /// "Plain"
@@ -55,9 +55,9 @@ save_ksudoku, would need to save those fields as well...
     pub puzzle_type: String,
     pub puzzle_type: String,
     /// Puzzle width (9, 16, 25).
     /// Puzzle width (9, 16, 25).
     pub order: u8,
     pub order: u8,
- }
+}
 
 
- impl Default for Ksudoku {
+impl Default for Ksudoku {
     fn default() -> Self {
     fn default() -> Self {
         Ksudoku {
         Ksudoku {
             puzzle: String::new(),
             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>> {
 pub fn load_ksudoku(filename: std::path::PathBuf) -> Result<Ksudoku, Box<dyn error::Error>> {
     let fh = File::open(filename)?;
     let fh = File::open(filename)?;
     let buffer = BufReader::new(fh);
     let buffer = BufReader::new(fh);
     let parser = EventReader::new(buffer);
     let parser = EventReader::new(buffer);
-    let mut element_name: String = String::new();    
+    let mut element_name: String = String::new();
     let mut details = Ksudoku::default();
     let mut details = Ksudoku::default();
 
 
     for e in parser {
     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();
                     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();
                     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() {
                     if !order.is_empty() {
                         let r = order.parse::<u8>();
                         let r = order.parse::<u8>();
@@ -155,7 +158,7 @@ pub fn save_ksudoku(
     event = WrXmlEvent::start_element("graph")
     event = WrXmlEvent::start_element("graph")
         .attr("specific-type", &data.specific_type)
         .attr("specific-type", &data.specific_type)
         .attr("type", &data.puzzle_type)
         .attr("type", &data.puzzle_type)
-        .attr("order", &order )
+        .attr("order", &order)
         .into();
         .into();
     writer.write(event)?;
     writer.write(event)?;
     event = WrXmlEvent::end_element().into();
     event = WrXmlEvent::end_element().into();
@@ -197,12 +200,11 @@ mod tests {
     use std::path::PathBuf;
     use std::path::PathBuf;
 
 
     fn test_files_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
         d
     }
     }
-    
+
     #[test]
     #[test]
     fn load_puzzles() {
     fn load_puzzles() {
         let mut testfile = test_files_path();
         let mut testfile = test_files_path();
@@ -218,21 +220,21 @@ mod tests {
         let mut board = AnyBoard::new(size);
         let mut board = AnyBoard::new(size);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         assert!(load.is_ok());
         assert!(load.is_ok());
-        
+
         // Ok, puzzle is loaded!
         // Ok, puzzle is loaded!
         let strings = board.to_strings();
         let strings = board.to_strings();
         assert_eq!(
         assert_eq!(
             strings,
             strings,
             vec![
             vec![
                 "  4   8  ",
                 "  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 board = AnyBoard::new(size);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         assert!(load.is_ok());
         assert!(load.is_ok());
-        
+
         // Ok, puzzle is loaded!
         // Ok, puzzle is loaded!
         let strings: Vec<String> = board.to_strings();
         let strings: Vec<String> = board.to_strings();
         assert_eq!(
         assert_eq!(
@@ -270,11 +272,11 @@ mod tests {
                 "OPC  N H   F J  ",
                 "OPC  N H   F J  ",
                 "  EHJ I   MA  CK",
                 "  EHJ I   MA  CK",
                 "     FM   IPA  D",
                 "     FM   IPA  D",
-                "FM  L    H    P "                
+                "FM  L    H    P "
             ]
             ]
         );
         );
         // board.display();
         // board.display();
-        
+
         let mut testfile = test_files_path();
         let mut testfile = test_files_path();
         testfile.push("25-puzzle");
         testfile.push("25-puzzle");
 
 
@@ -288,12 +290,12 @@ mod tests {
         let mut board = AnyBoard::new(size);
         let mut board = AnyBoard::new(size);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         let mut load = board.load_from_tld('b', '_', &result.puzzle);
         assert!(load.is_ok());
         assert!(load.is_ok());
-        
+
         // Ok, puzzle is loaded!
         // Ok, puzzle is loaded!
         // board.display();
         // board.display();
         let strings: Vec<String> = board.to_strings();
         let strings: Vec<String> = board.to_strings();
         assert_eq!(
         assert_eq!(
-            strings, 
+            strings,
             vec![
             vec![
                 " T DPF    Y H R    WSX L ",
                 " T DPF    Y H R    WSX L ",
                 "L QF     J X C G     UB D",
                 "L QF     J X C G     UB D",
@@ -320,9 +322,7 @@ mod tests {
                 " FR H DQOC  K  INBW T UY ",
                 " FR H DQOC  K  INBW T UY ",
                 "T JL     G I P F     OC W",
                 "T JL     G I P F     OC W",
                 " B KMV    Q J H    GRI E "
                 " B KMV    Q J H    GRI E "
-        
             ]
             ]
-        );        
-
+        );
     }
     }
-}
+}

+ 125 - 70
sudoku/src/sudoku.rs

@@ -445,11 +445,13 @@ impl AnyBoard {
         solutions.reserve(max as usize);
         solutions.reserve(max as usize);
         workset.brute_force(&mut total_solutions, &mut solutions, &groups);
         workset.brute_force(&mut total_solutions, &mut solutions, &groups);
 
 
+        /* 
         if solutions.len() > 0 {
         if solutions.len() > 0 {
             println!("*** A Solution:");
             println!("*** A Solution:");
             solutions[0].display();
             solutions[0].display();
             println!("***");
             println!("***");
         }
         }
+        */
         (total_solutions, solutions)
         (total_solutions, solutions)
     }
     }
 
 
@@ -711,6 +713,7 @@ impl AnySolver {
     /// Validate the board
     /// Validate the board
     /// Reuse reset_possible code, verify the values are possible.
     /// Reuse reset_possible code, verify the values are possible.
     /// - This does not check if the board is completed.
     /// - This does not check if the board is completed.
+    /// - It does check that blanks have possible values.
     pub fn validate_board(&mut self) -> bool {
     pub fn validate_board(&mut self) -> bool {
         let mut has_blanks = false;
         let mut has_blanks = false;
 
 
@@ -724,9 +727,9 @@ impl AnySolver {
                         // I was going to reset_possible, but the board is broken!
                         // I was going to reset_possible, but the board is broken!
                         // Leave in a broken state.
                         // 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;
                         return false;
                     }
                     }
                     self.process_move(x, y, value);
                     self.process_move(x, y, value);
@@ -746,8 +749,9 @@ impl AnySolver {
                     if value == 0 {
                     if value == 0 {
                         let count = self.possible.possible[self.possible.pos(x, y)].count_set();
                         let count = self.possible.possible[self.possible.pos(x, y)].count_set();
                         if count == 0 {
                         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;
                             return false;
                         }
                         }
                     }
                     }
@@ -858,81 +862,92 @@ impl AnySolver {
         true
         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;
                             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  ",
                 "OPC  N H   F J  ",
                 "  EHJ I   MA  CK",
                 "  EHJ I   MA  CK",
                 "     FM   IPA  D",
                 "     FM   IPA  D",
-                "FM  L    H    P "                
+                "FM  L    H    P "
             ]
             ]
         );
         );
         // board.display();
         // board.display();
@@ -1006,7 +1021,7 @@ mod tests {
         // board.display();
         // board.display();
         let strings: Vec<String> = board.to_strings();
         let strings: Vec<String> = board.to_strings();
         assert_eq!(
         assert_eq!(
-            strings, 
+            strings,
             vec![
             vec![
                 " T DPF    Y H R    WSX L ",
                 " T DPF    Y H R    WSX L ",
                 "L QF     J X C G     UB D",
                 "L QF     J X C G     UB D",
@@ -1033,7 +1048,6 @@ mod tests {
                 " FR H DQOC  K  INBW T UY ",
                 " FR H DQOC  K  INBW T UY ",
                 "T JL     G I P F     OC W",
                 "T JL     G I P F     OC W",
                 " B KMV    Q J H    GRI E "
                 " B KMV    Q J H    GRI E "
-        
             ]
             ]
         );
         );
         let mut solver = AnySolver::new_from(&board);
         let mut solver = AnySolver::new_from(&board);
@@ -1053,11 +1067,42 @@ mod tests {
         assert_eq!(9, board.width);
         assert_eq!(9, board.width);
         assert_eq!(81, board.max_index);
         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);
         let mut solver = AnySolver::new_from(&board);
         assert!(solver.validate_board());
         assert!(solver.validate_board());
         let solutions = solver.board.brute_force_solver(2);
         let solutions = solver.board.brute_force_solver(2);
         assert!(solutions.0 == 1, "Expected 1 solution, got {}", solutions.0);
         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
         // 4x4 board takes 40 minutes
 
 
@@ -1093,6 +1138,7 @@ mod tests {
         let mut solver = AnySolver::new(3);
         let mut solver = AnySolver::new(3);
         solver.make();
         solver.make();
         solver.board.display();
         solver.board.display();
+        assert!(solver.validate_board());
     }
     }
 
 
     #[test]
     #[test]
@@ -1281,6 +1327,15 @@ impl<'a> BoardPossible<'a> {
 }
 }
 */
 */
 
 
+/*
+  ___  _     _    ____          _      
+ / _ \| | __| |  / ___|___   __| | ___ 
+| | | | |/ _` | | |   / _ \ / _` |/ _ \
+| |_| | | (_| | | |__| (_) | (_| |  __/
+ \___/|_|\__,_|  \____\___/ \__,_|\___|
+
+ */
+
 #[derive(Debug, Clone, Copy)]
 #[derive(Debug, Clone, Copy)]
 pub struct Sudoku {
 pub struct Sudoku {
     pub board: [u8; MAX_SIZE as usize],      // Board
     pub board: [u8; MAX_SIZE as usize],      // Board