CS 261 Homework 5 - Sudoku Solver
Due Mon Nov 03 at 11:59pm
Overview
Sudoku is a popular puzzle game in which the player tries to fill a 9x9 grid of numbers with the digits 1 through 9, so that a digit occurs only once in each row, each column, and each 3x3 sub-grid. In this assignment, you will be writing a program that can solve an arbitrary Sudoku puzzle using a brute-force, recursive backtracking algorithm. Solutions will be displayed on the command-line, so that your program's output should look like this (puzzle from Wikipedia):
Solving Puzzle From Wikipedia +-------+-------+-------+ | 5 3 . | . 7 . | . . . | | 6 . . | 1 9 5 | . . . | | . 9 8 | . . . | . 6 . | +-------+-------+-------+ | 8 . . | . 6 . | . . 3 | | 4 . . | 8 . 3 | . . 1 | | 7 . . | . 2 . | . . 6 | +-------+-------+-------+ | . 6 . | . . . | 2 8 . | | . . . | 4 1 9 | . . 5 | | . . . | . 8 . | . 7 9 | +-------+-------+-------+ +-------+-------+-------+ | 5 3 4 | 6 7 8 | 9 1 2 | | 6 7 2 | 1 9 5 | 3 4 8 | | 1 9 8 | 3 4 2 | 5 6 7 | +-------+-------+-------+ | 8 5 9 | 7 6 1 | 4 2 3 | | 4 2 6 | 8 5 3 | 7 9 1 | | 7 1 3 | 9 2 4 | 8 5 6 | +-------+-------+-------+ | 9 6 1 | 5 3 7 | 2 8 4 | | 2 8 7 | 4 1 9 | 6 3 5 | | 3 4 5 | 2 8 6 | 1 7 9 | +-------+-------+-------+
Your program will read in a text file that contains a list of puzzles (yes, more than one) and then will solve each in turn, printing the puzzles and their solutions.
Moreover, in order to help you practice writing recursive programs, your program must not contain any iteration: no for
or while
loops are allowed! You are welcome to use iterative loops to test and debug your program (or to temporarily fill in functionality)---however, these loops must all be excised from your final submitted program.
This assignment should be completed individually. You are welcome to ask for help from me, or from your classmates (but remember to follow the Gilligan's Island rule)!
Objectives
- To practice writing recursive methods
- To practice writing backtracking algorithms
Necessary Files
You will want a copy of the Hwk5.zip file which contains text files of Sudoku puzzles that you should use in writing your program.
- Each puzzle is formatted as ten lines of text: a line with a label (which separates the puzzles), then 9 lines of 9 comma-separated integers that represents the puzzle--blank spots in the puzzle are represented with 0s. There are three included puzzle files:
sudoku_wiki.txt
that contains the Wikipedia puzzle displayed above,sudoko_easy.txt
that contains 50 "easy" puzzles, andsudoku_hard.txt
that contains 95 "hard" puzzles. Your program should be able to solve all the puzzles in these files (though it may take a tad longer for the hard puzzles!)
Additionally, the zip file also contains the README.txt for this assignment, which you will need to fill out.
Assignment Details
Read through all of these instructions carefully before you begin! They detail the specifics of what you will need to implement to complete this assignment. They list steps you'll need to do, though you do not need to follow this particular order. Make sure you thoroughly testeach section of your program--this assignment lends itself to checking the functionality of each method before moving on, and making sure each piece works will help avoid more complicated bugs later!
-
Your first step should be to create an ADT (e.g.,
SudokuPuzzle
) to represent a single sudoku puzzle that can be solved. Much of your logic will go in this class.- I recommend you store the data as a sudoku puzzle using an
int[][]
; that will give you quick and easy access to any particular entry in the puzzle. -
Useful tip! Make a note about whether you will access items in your array as
array[col][row]
orarray[row][col]
. Since puzzles are square either one is fine, but you'll want to make sure you are consistent.
- I recommend you store the data as a sudoku puzzle using an
-
You will need to have a way to load a file into a Sudoku puzzle. I recommend you make some prototyping code that iteratively reads a file and parses it into the puzzle; this will let you get something working without needing to worry about recursive file parsing (which can get a bit tricky; see below).
- Since thise code will contain forbidden loops, you will need to eventually throw it away. That is okay! We often write up sample code to test a concept or act as a placeholder, even if it isn't used in our final project. You should become willing to discard old code when necessary.
- Note that you are allowed to use built-in methods that use iteration. For example, the String.split() method can be very useful for breaking apart each row into individual numbers.
-
Write a method to recursively print out the puzzle you have read in. Follow the recipes in the textbook and that we discussed in class: remember to include a stopping condition and a recursing condition.
- Hint: you'd normally use a nested loop to print out a 2d array; therefore you'll need some kind of "nested" recursion. Consider writing two recursive methods: one that recursively prints a single row of numbers, and one that recursively prints all the rows (by calling the previous method).
- Yes, you should print the border to segment off the squares as in the example at the top of the page--this helps with checking that the puzzle is solved correctly!
- Note that having loaded and printed a puzzle is a good target for Fri Oct 24.
-
In order to solve the puzzle, you'll need to know whether you can place a given number in a particular spot in the puzzle. Write a method that recursively determines this.
- Hint: A number can be invalid if it is already in the same row, the same column, or the same sub-grid or "box". Try writing three different helper methods that check these three conditions, and then calling all of them from a main "is valid" method
-
Checking the "box" can be tricky. You might think about the box as being a set of "short" (length 3) rows, and then simply using the same method that you use to check if an entry can be put in any of the rows! The number will only be valid if all of the "subrows" are valid.
- And you can of course recursively loop through the subrows ;) Do not hard-code assumptions about the puzzle size!
-
Major style tip! This functionality is where the importance of good method names and detailed JavaDoc comments can pay off in spades.
- Name your methods so that they explain clearly what they do.
checkRow
doesn't tell us anything about how to use the method;canPlaceInRow
on the other hand makes it very clear how the method will be used! - Write out the JavaDoc for your method before you fill in the details of the method. Note what the parameters and the return value represent before you write any code--that way you'll have it very clearly documented what any
row
parameter represents!
- Name your methods so that they explain clearly what they do.
- Be sure to test your code thoroughly--this is an easy place to make a mistake! You should actually do this! Include a main method in the class with code that demonstrates that your methods work (remember: print the expected and actual output of the method!).
- This functionality is a good target for Mon Oct 27.
-
Now the core of the assignment: write a recursive backtracking method that solves the sudoku puzzle.
-
Backtracking can be tricky to get the hang of. The process works basically like this:
- try and solve the puzzle by placing a number.
- after placing the number try and recursively solve the rest of the puzzle (placing a number in the next spot)
- if that solved the puzzle, then report back that the puzzle is solved
- otherwise, remove that number because it didn't lead to a solution, and try to recursively solve the puzzle with the next number
The key trick of backtracking is that each time you recusively "solve" the puzzle, you need to report back (
return
) whether or not the puzzle is solved. Basically your method will place a number, and then "delegate" to another method call to ask if everything else worked. If it did, then you won. But it it didn't, then you need to tell your boss (who delegated to you) that your number was bad. We'll go over this process more in class.Like recursion itself, backtracking is both simple and complex. Effectively you will have you will have two recursive conditions: one if the number does work (in which case you try to solve the rest of the puzzle), and one if the number does not work (in which case you may need to try another number).
- Test this method thoroughly. The Eclipse Debugger can be especially helpful here, as it will let you see what your puzzle looks like at any given point in the algorithm. Put a break point at the start of each method call, and inspect if the numbers placed in the puzzle are following the "brute force" strategy. (You can also print out the puzzle at every step, which can sometimes be easier than reading puzzle state from the debugger). You'll know when it works, because it will provide you with a completed puzzle!
- You should be sure to have this completed by Fri Oct 31.
-
-
Now that you are able to solve a puzzle and should be feeling good about recursion, You will need to add functionality to recursively parse and solve all of the puzzles from a file. This is simpler than it sounds.
- Create a new ADT (e.g.,
SudokuSolver
) that can represent the collection of solveable puzzles. - Having one method that loads all the puzzles into a data structure (think: what kind of datastructure would be good for representing lots of puzzles?), then a separate method that recursively calls "solve" on each of those puzzles can help keep things from being overwhelming.
- Create a new ADT (e.g.,
-
The hardest part of this is being able to parse a file recursively. The trick is to create a single
Scanner
on the file, and then pass that object as a parameter to your recursive methods. Since the Scanner remembers which line it is currently on, you can simply advance it one line when you need to, and all your methods will be able to work off the same file.- Start by being able to parse a file with a single puzzle (like your temporary code). Go to that code and replace the loops with recursive methods. This is a great exercise in adapting iterative methods into recursive ones!
- Again, since you normally have nested loop, you will likey want to make a helper method that recursively reads through each line.
- Once you have a method that can parse a single puzzle, recursively call that method (passing in the same
Scanner
) to parse all the puzzles! - This functionality, along with the rest of your program, should be completed by Mon Nov 03
- Be sure to complete the README.txt that you found in the Hwk5.zip file, and upload it along with your code!
Some helpful notes about style
- Name your methods so that they explain clearly what they do.
checkRow
doesn't tell us anything about how to use the method;canPlaceInRow
on the other hand makes it very clear how the method will be used! - Use "launcher" or "wrapper" methods to start off your main recursive functions; this will help with readability.
- Write out the JavaDoc for your method before you fill in the details of the method. Note what the parameters and the return value represent before you write any code--that way you'll have it very clearly documented what that
row
parameter represents! - Using appropriate variable and method names can help you keep track of what is going on with each method. Methods will have a lot of parameters, so give them descriptive names so you remember which is which! It's easy to "refactor" to rename variables without needing to jump around in your program.
- Do not create extraneous instance variables. Not only does it make for ugly code, but it can introduce bugs when using recursive methods (since each call may be accessing the field at the same time). Instead, you should pass information to subsequent recursive calls in the parameter to that method call. This is why recursive methods gain a lot of parameters!
- Include copious comments about what each call does and when it does it to help you keep track of your program.
Submitting
BEFORE YOU SUBMIT: make sure your code is fully documented and functional! If your code doesn't run, I can't give you credit! Your name should be in the class comment at the top of each class.
Submit your program to the Hwk5 submission folder on vhedwig. Make sure you upload your work to the correct folder!. You should upload your entire src folder with all your classes. Be sure to complete the provided README.txt file with details about your program. Also include any extra puzzle files you found or used.
The homework is due at midnight on Monday, Nov 03.
Extensions
- Modify your program to print out all possible solutions to a Sudoku puzzle. I have not checked if any of the puzzles in the provided lists have multiple solutions; you may need to find examples of such puzzles online to test your program with.
- Add functionality to generate Sudoku puzzles. There are a number of algorithms for this: one basic idea is to place a numbers randomly, and then check that the puzzle has a unique solution. If there isn't a solution, backtrack!
- Of course, this solver can be used to support a program in which the player tries to solve a puzzle, and the program either tells them if they are correct or otherwise gives hints. Such an extension goes well beyond the scope of the assignment.
Grading
This assignment will be graded on approximately the following criteria:
This assignment will be graded out of 30 points. Note no credit will be given for programs that utilize for or while loops.
- Functionality (21pt)
- [2pt] Your program recursively prints a Sudoku puzzle
- [1pt] Your printed puzzle includes a border and placeholders for "empty" entries
- [1pt] Your program recursively checks the validity of a number for a particlar spot
- [1pt] Your program accurately checks if a number can be placed in a row
- [1pt] Your program recursively checks if a number can be placed in a column
- [2pt] Your program recursively checks if a number can be placed in a subgrid (a "box")
- [3pt] Your program recursively "brute-forces" potential solutions to the Sudoku puzzle, across all rows and columns
- [5pt] Your program recursively "backtracks" when a solution is found to be invalid
- [2pt] Your program recursively parses a Sudoku puzzle from a file
- [1pt] Your program recursively parses multiple files
- [1pt] Your program recrusively solves all the puzzles in a file
- [1pt] Your program prints out both the unsolved and the solved sudoku puzzle
- Style (5pt)
- [2pt] You include and utilize recursive "launcher" methods to keep your code clean
- [1pt] You have used descriptive method names (that indicate how the method will be used!)
- [1pt] You use appropriate encapsulation and private helper methods in your classes
- [1pt] You have written extensible code, and not hard-coded details about the puzzle size
- Documentation (4pt)
- [2pt] All methods include JavaDoc comments with appropriate JavaDoc tags (
@param
and@return
). - [1pt] You have included sufficient inline comments explaining your code.
- [1pt] The README is completed