Assigned: | Monday, September 16 |
First version due: | Tuesday, October 2, 11:59pm |
Second version due: | Friday, October 25, 11:59pm |
Read the entire assignment before you start working on it. It might appear very large and difficult at first. But if you do it the way that I have suggested you will only need to write roughly 250-300 nonblank lines. Just work your way forward, one step at at time.
A Sudoku puzzle is a 9x9 grid. Some of the cells have digits in them, and some are empty. The grid naturally breaks into rows and columns, but it is also broken into nine 3x3 squares. For example, see here for sample puzzles. The goal is to fill in a digit into each of the empty cells, with the following requirements.
Write a C++ program that reads a Sudoku puzzle and prints a solution to
that puzzle. The puzzle is presented as 9 lines, with 9 nonblank characters
per line. A digit represents an initially occupied cell, and a dash represents
an empty cell. There can be empty lines, which are skipped, and there can
be spaces in a line, which are also skipped.
The input might be as follows.
1-- 489 --6
73- --- -4-
--- --1 295
--7 12- 6--
5-- 7-3 --8
--6 -95 7--
914 6-- ---
-2- --- -37
8-- 512 --4
You cannot assume that there are spaces or empty lines. So the input might
also be as follows.
1--489--6
73-----4-
-----1295
--712-6--
5--7-3--8
--6-957--
9146-----
-2-----37
8--512--4
The output should consist of the original puzzle followed by the
the solved puzzle, in a similar style. Please
write the output with spaces and line breaks the way it was shown in the
first input example, regardless of the original form of the input. For example,
the output might be as follows.
The original puzzle:
1-- 489 --6
73- --- -4-
--- --1 295
--7 12- 6--
5-- 7-3 --8
--6 -95 7--
914 6-- ---
-2- --- -37
8-- 512 --4
Solution:
152 489 376
739 256 841
468 371 295
387 124 659
591 763 428
246 895 713
914 637 582
625 948 137
873 512 964
The puzzle was solved.
You will want to store a set of possible values for each cell. You can use the following implementation of sets. Get both files.
To use sets, include intset.h in your program, as follows.
#include "intset.h"(Notice the quotes instead of <...>. The quotes tell the compiler to look for intset.h in the directory that this file is in, while <...> tell the compiler to look in the standard library directory.)
You can use the following capabilities.
SetOfSmallInts
emptySet
SetOfSmallInts s; s = emptySet;creates a variable s, and explicitly initializes it to hold an empty set. (The initialization is redundant, since s is initialized to an empty set automatically when it is created.)
isEmpty(s)
rangeSet(x,y)
SetOfSmallInts s; s = rangeSet(1,9);makes set s = {1,2,3,4,5,6,7,8,9}.
singletonSet(x)
SetOfSmallInts t; t = singletonSet(1);makes t hold set {1}.
isSingleton(s)
onlyMember(s)
SetOfSmallInts s = singletonSet(3); int n = onlyMember(s);makes n = 3.
smallest(s)
SetOfSmallInts s = rangeSet(3,6); int n = smallest(s);makes n = 3.
member(x,s)
setUnion(s,t)
SetOfSmallInts s, t, u; s = singletonSet(2); t = singletonSet(6); u = setUnion(s,t);makes u hold set {2,6}.
setIntersection(s,t)
SetOfSmallInts s,t,u; s = rangeSet(1,5); t = rangeSet(3,7); u = setIntersection(s,t);makes u = {3,4,5}.
setDifference(s,t)
SetOfSmallInts s,t,u; s = rangeSet(1,5); t = rangeSet(3,7); u = setDifference(s,t);makes u = {1,2}.
insert(x,s)
SetOfSmallInts u,v; u = singletonSet(3); v = insert(5, u);makes u = {3} and v = {3,5}. Notice that creating v does not change u.
remove(x,s)
size(s)
A puzzle is a 9x9 array of sets. If you include definition
typedef SetOfSmallInts Puzzle[9][9];then you can use Puzzle as a type, indicating a 9x9 array of sets. If p has type Puzzle, then p[i][j] is the set stored at row i, column j of p. The rows and columns are numbered from 0 to 8, not from 1 to 9. So the upper left corner of puzzle p is p[0][0] and the lower right corner is p[8][8].
To get you going, here is a function that copies a puzzle. You can copy it into your program. (Just copy and paste. Do not copy it by hand! Be sure to copy the comment too, not just the function definition.) Read the function definition to see how it works.
/**************************************************************** * copyPuzzle * **************************************************************** * Copy puzzle p into q. For example, if p is a puzzle, then * * Puzzle q; * * copyPuzzle(q, p); * * stores a copy of puzzle p into q. * ****************************************************************/ void copyPuzzle(Puzzle q, Puzzle p) { int i, j; for(i = 0; i < 9; i++) { for(j = 0; j < 9; j++) { q[i][j] = p[i][j]; } } }
You will find it useful to be able to extract a row, column or square of a puzzle, as an array of 9 sets. But you need to be able to change those sets, not just to look at them. So what you really want is a pointer back into the original array, so that any changes you make affect the original array. You can do that as follows. Copy the following into your program. (Just copy and paste. Do not copy it by hand! Copy the comments too.)
typedef SetOfSmallInts* PuzzleSection[9]; /**************************************************************** * getRow * **************************************************************** * Store the i-th row of puzzle p into puzzle section R. * * The rows are numbered from 0 to 8. * * * * After doing this, the k-th set in row i is *(R[k]). * * Do not omit *(...). The cells in the row are numbered * * 0,1,...,8. * ****************************************************************/ void getRow(PuzzleSection R, Puzzle p, int i) { int j; for(j = 0; j < 9; j++) { R[j] = &(p[i][j]); } } /**************************************************************** * getColumn * **************************************************************** * Store the j-th column of puzzle p into puzzle section R. * * The columns are numbered from 0 to 8. * * * * After doing this, the k-th set in column j is * * *(R[i]). Do not omit *(...). The cells in the * * column are numbered 0,1,...,8. * ****************************************************************/ void getColumn(PuzzleSection R, Puzzle p, int j) { int i; for(i = 0; i < 9; i++) { R[i] = &(p[i][j]); } } /**************************************************************** * getSquare * **************************************************************** * Store the k-th square of puzzle p into puzzle section R. * * The squares are numbered as follows. * * 0 1 2 * * 3 4 5 * * 6 7 8 * * For example, square 4 is the middle square in the puzzle. * * * * After doing getSquare, the i-th set in the square is *(R[i]).* * Do not omit *(...). The cells in the square are numbered * * 0,1,...,8, in the same pattern shown above for the squares * * themselves. For example *(R[3]) is the first position in * * the second row of the square. * ****************************************************************/ void getSquare(PuzzleSection R, Puzzle p, int k) { int i; for(i = 0; i < 9; i++) { R[i] = &(p[k - k%3 + i/3][3*(k%3) + i%3]); } }
Suppose that p has type Puzzle.
PuzzleSection Sec; getSquare(Sec, p, 0);makes Sec hold the square in the upper-left corner of puzzle p (square 0). The members of array Sec refer to cells in puzzle p, as follows.
Section entry | Corresponds to |
---|---|
*(Sec[0]) | p[0][0] |
*(Sec[1]) | p[0][1] |
*(Sec[2]) | p[0][2] |
*(Sec[3]) | p[1][0] |
*(Sec[4]) | p[1][1] |
*(Sec[5]) | p[1][2] |
*(Sec[6]) | p[2][0] |
*(Sec[7]) | p[2][1] |
*(Sec[8]) | p[2][2] |
For example, statement
*(Sec[4]) = setDifference(*(Sec[4]), s);has the same effect as
p[1][1] = setDifference(p[1][1], s);
Start by writing three functions, one to read a puzzle and two to write a puzzle. Use the following headings.
void readPuzzle(Puzzle p)
Read a puzzle from the standard input (cin, if you use the iostream library) in the form shown above. Store it into p.
If variable c has type char, then
cin >> c;
reads the next nonblank character and stores it
into c. That is an easy way to read the input,
one character at a time. What set should you store if
you see a dash? Keep in mind that a dash means, as far
as you know right now, any number from 1 to 9 might
go in this cell.
void printPuzzle(Puzzle p)
Print puzzle p on the standard output (cout, if you use the iostream library), in a form suitable for the final output. If a set is a singleton set, write the number in that set. If it is not singleton, write '-'. If it is empty, write 0. You should be able to use this to show both the original puzzle and the solved puzzle.
void showPuzzle(Puzzle p)
Print puzzle p on the standard output, but in a form suitable for
debugging. For each set, print all of the members of the set. Make
the output look nice. Keep the columns aligned. For example, a puzzle might
look as follows when printed using showPuzzle.
1 5 25 4 8 9 3 7 6
7 3 259 2 5 6 18 4 1
46 46 8 3 7 1 2 9 5
34 489 7 1 2 4 6 5 39
5 49 129 7 46 3 149 12 8
234 48 6 8 9 5 7 12 123
9 1 4 6 37 78 58 258 2
6 2 5 89 4 48 158 3 7
8 7 3 5 1 2 9 6 4
You can see that the first square contains
{1} {5} {2,5}
{7} {3} {2,5,9}
{4,6} {4,6} {8}
Design showPuzzle so that, if it encounters an empty set, it shows 0 rather
than showing nothing at all.
Also write a main program that reads a puzzle and prints it back out using both printPuzzle and showPuzzle. Make sure that all three of these functions appear to be working before moving on. Resist the temptation to move on to the rest before you are ready. Here are some puzzles for testing your program.
To build your program, copy file Makefile to the directory containing your program. It contains a few targets to make. For example, command
make sudokucompiles and links the program. Command
make run1compiles and runs the program on puzzle1.txt. Command
make testruns the program on puzzle1.txt, puzzle2.txt and puzzle3.txt.
There are a few tactics that people (and computers) use to solve Sudoku puzzles. A simple and obvious tactic is to locate singleton sets, which are cells whose values are known. If K occurs in a singleton set, then K cannot occur in any other set in the same row, column or square. So remove it from them all.
A simple way to run this tactic is to do a loop from 0 to 8. At value j (0 to 8) run tactic 1 on row j, column j and square j. Do each of those by extracting a section of the puzzle and running tactic 1 on the section.
To run tactic 1 on a section, look at each set in the section. If set number i is {K}, then go through all indices, skipping index i, removing K from the set.
You will need to know whether doing tactic 1 has made any change to the puzzle. The functions should tell you that information.
So there are two functions to write.
bool tacticOneOnSection(PuzzleSection sec)
bool tacticOne(Puzzle p)
Write a clear and precise contract for each of your functions.
Modify your main program so that it echoes the original puzzle then runs tactic 1 once on each row, column and square, and then shows the puzzle using showPuzzle. Note that the puzzle will probably not be solved yet. But check whether tactic 1 appears to be doing something sensible.
Now write another function (let's call it the solver) whose job is to find a solution to a given puzzle. The puzzle should be a parameter. Make it run tactic1 on every row, column and square repeatedly until tactic 1 does not make any more progress.
There are three possible outcomes of the solver.
It is possible that all of the sets are singleton. That means a solution has been found.
It is possible that the original puzzle did not have a solution. (Your solver should be prepared to deal with unsolvable puzzles.) When that happens, at least one of the sets in the puzzle is an empty set, indicating that no values can go in that cell.
It is possible that neither of the above cases holds. That means there is more work to be done.
Make the solver return a value indicating which of those cases
hold. So there are three possible return values. To handle that,
we will use an enumerated type. Definition
enum SolutionStatus {solved, unsolvable, working};
creates a new type, SolutionStatus, with three values, solved, unsolvable
and working. Make your solver return a result of type SolutionStatus.
For example, its heading would be
SolutionStatus solver(Puzzle p)
To make the solver return value solved, say
return solved;
You will want functions to test the status. Write a function that takes a puzzle and returns true if all of the sets are singleton. Write another function that takes a puzzle and returns true if there is an empty set in the puzzle.
Modify the main program so that it calls the solver. Make it print the solved puzzle, then report the status: either solved, or unsolvable, or still a work in progress.
Test your program. You should find that it solves puzzle1.txt
completely, but cannot finish puzzle3.txt. Command
./sudoku <puzzle1.txt
runs the sudoku program with its input coming from file
puzzle1.txt.
Professional software designers write their programs for testing, right from the start. They do everything they can to make them work, but then presume that there will be problems. (There usually are.) The idea is not to wait until problems show up to think about testing. Think about it from the beginning.
One way to think about testing from the start is to build in
tracing capabilities. If you write
#ifdef DEBUG
if(tracing)
{
cout << "tactic 1: here is the initial puzzle\n";
showPuzzle(p);
}
#endif
inside tactic 1, then you make tactic 1 show what it is
doing. Of course, you do not always want it to do that.
So you have two switches to turn it on or off.
If you have written
#define DEBUG
then the part between #ifdef DEBUG and #endif will be compiled
into the program. But if you do not write that, or write
//#define DEBUG
then the compiler will ignore the part between #ifdef DEBUG
and #endif, and it will be as if that part were not part of
the program at all.
Variable tracing has type bool. If it is true, then the trace will be shown (assuming it has not been skipped over by the compiler.). If tracing is false, nothing is shown.
Using this idea, add debug prints to function tacticOne. Show the puzzle at the beginning and at the end of the function. But make sure that it can be turned off. Never print raw information, such as numbers. Always say which function is showing it, and what it represents.
When you turn in your program, do not remove the debug prints. The point is to leave them in the program so that they can be turned on when they are needed. But ensure that debug prints are turned off in the version that you submit.
Some puzzles will still remain unsolved after tactic 1 has nothing more to contribute. But there is another commonly employed tactic that can help. Suppose that you look at all of the sets in a given row, and only one of those sets contains 2. Then, clearly, that cell must hold 2. So you change it to {2}, a singleton set.
You can do the same for every row, column and square, and for every number from 1 to 9. Write a function that does this to a section, by checking, for every number k from 1 to 9, whether there is just one set in the section that holds k. If so, it should make that set be the singleton set {k}. Then write another function that does tactic 2 on each row, column and square in a puzzle.
Your new functions should return true if they made a change and false if they did not make a change.
Modify your solver to work as follows.
(This is a description of the algorithm, in pseudo-code,
not a C++ program.)
1. Loop (main loop)
a. If all of the sets in the puzzle are singleton, return solved.
b. If at least one of the sets is empty, return unsolvable.
c. Run tactic 1 once on every row, column and
square in the entire puzzle once. Remember whether it
made any progress.
d. Run tactic 2 once on every row, column and
square in the entire puzzle once. Remember whether
it made any progress.
e. If neither tactic 1 nor tactic 2 made any progress,
then exit the main loop.
end loop
2. Return working
Test this. On puzzle 1, it should return solved. Try it on puzzle 3. The program will not finish puzzle 3, and the solver should return working. If you turn on debug prints you can see the puzzle at every step.)
Do not move on until your program seems to be working so far. Do not take it for granted that it works. Look at what your program is doing with a critical eye. Are the tactics doing sensible things? Are your debug prints readable, so that you can tell?Many puzzles can be solved using only tactics 1 and 2. But some, such as puzzle 3, are more stubborn. We will end with a tactic that is guaranteed to finish the job, for every puzzle.
Suppose that, after running tactics 1 and 2 until they no longer help, there are still nonsingleton sets, but no empty sets. Then find one of the nonsingleton sets, and try each possible value for it.
Of course, some of your choices will not work. So you have to consider the possibility that a puzzle is not solvable, even if the original puzzle that you started with is solvable. Fortunately, you have already taken that into account.
Write a function that performs tactic 3 on a puzzle. It should return a result of type SolutionStatus. It can return either solved or unsolvable, but it will never return working, because it always reaches a conclusion.
Tactic 3 works roughly as follows, where p is the puzzle that it is working on.
for i = 0, ..., 8
for j = 0, ..., 8
if p[i][j] is not a singleton set
comment: try values for set p[i][j]
for k = 1, ..., 9
if k is a member of p[i][j]
Make a copy (q) of puzzle p.
q[i][j] = {k} (a singleton set -- this is where we try k)
Do a debug print showing that p[i][j] = k is being tried.
Run the solver on q.
If the solver returned solved, then
Copy q back into p
Return solved
end if
Do a debug print explaining that p[i][j] = k did not work.
end if
end for
return unsolvable (none of the values for p[i][j] work, so
the puzzle has no solution)
end if
end for
end for
It is important to work on a copy of the puzzle, so that the changes that you make will not ruin what you have. Also, notice that tactic 3 only tries values for the first non-singleton set that it encounters. If it cannot find a value to use for that set, there is no point in going ahead and looking for values for other sets. All it takes is one spot that cannot be filled in to make the puzzle unsolvable. That is why tactic 3 returns unsolvable if it finishes the k-loop without returning success.
Modify the solver too so that it uses tactic 3. Here is
a sketch.
1. Loop (main loop)
a. If all of the sets in the puzzle are singleton, return solved.
b. If at least one of the sets is empty, return unsolvable.
c. Run tactic 1 once on every row, column and
square in the entire puzzle once. Remember whether it
made any progress.
d. Run tactic 2 once on every row, column and
square in the entire puzzle once. Remember whether
it made any progress.
e. If neither tactic 1 nor tactic 2 made any progress,
then exit the main loop.
end loop
3. Run tactic 3 on the puzzle and return the same answer as
tactic 3 returns.
Notice that you only try tactic 3 after tactics 1 and 2 have nothing
more to contribute. The
reason is that tactic 3 is relatively expensive, and you would prefer to use
it only when the cheaper tactics 1 and 2 cannot help.
Note. The solver uses tactic 3, and tactic 3 uses the solver. That is allowed. It is called mutual recursion. But you will need to help the compiler, so that you can use a function before it is defined. If you want to use the solver before it is defined, copy the heading of the solver to an earlier place in the program, and write a semicolon at the end of the heading. That tells the compiler that the definition of the solver is coming, but lets you use the solver before you define it. (It is called a prototype.) For example, write
SolutionStatus solver(Puzzle p);somewhere before the occurrence of tactic 3, so that tactic 3 can use solver.
Test your program. Do not just presume that it works. Try it on an obviously unsolvable puzzle. Does it say that the puzzle is unsolvable?
Software needs to be tested extensively. Any time you make a modification, you really should go back and redo all of your tests to make sure that you have not inadvertantly ruined something that used to work. But you do not want to do that by hand; that is far too much work. So you put together a suite of tests, called regression tests, that run automatically. You generally leave tests in the suite, even after they work, unless they are testing something that the software no longer supports.
The Makefile that I supplied contains a target called test so that command make test runs ./sudoku on each of puzzle1.txt, puzzle2.txt and puzzle3.txt. That is a small regression test.
(Regression tests are normally done with debugging turned off. You
only turn debugging on when a test does not work and you want to find
out why not. Ideally, each test checks its own work and you only
get output shown if the test is incorrect.
Yours won't do that. But if
you did want to automate checking,
you would run another program that captures the standard output from
sudoku and checks it. For example, command
./sudoku <puzzle1.txt | checkpuzzle puzzle1.ans
sends the standard output of the sudoku program to the standard
input of the checkpuzzle program. That is, checkpuzzle sees
in its standard input just what sudoku writes to its standard output.
You would write checkpuzzle
to read the information, possibly along with a prewritten
answer file (puzzle1.ans)
and to check whether the two are identical.)
Here are some things to do and mistakes to avoid, with a range of points that you might lose (out of 100) for each one.
Write your name, the course and the assignment number in a comment at the top of the program, along with the tab stop distance. (up to 2 points)
Failure to compile (100 points). Your program is expected to compile. A program that has fatal compile errors will receive a grade of 0.
Compiler warnings (up to 5 points). Your program should not have any compiler warnings.
Failure to follow the design (up to 100 points). The design requires you two write certain functions. It also says what those functions must do. It is not acceptable to substitute a different design. If you use a completely different design you will receive no points.
The design does not call for global variables. Do not use global variables. Using even one global variable means that you are not following the design, and can lead to a very high loss of points.
Failure to write one or more tactics (up to 15 points each). You are expected to provide all three tactics, and to use them.
Failure to use puzzle sections (up to 7 points). Do not write each tactic directly for a row, then for a column, then for a square. You end up duplicating ideas. That is a problem because it makes a program difficult to modify. Any modification needs to be done on each copy.
Incorrect results (up to 30 points). The program should produce correct results.
Violating the abstraction of the SmallSetOfInts type (up to 20 points). You can use type SmallSetOfInts and the functions described on this page, but that is all. If s has type SmallSetOfInts and your program mentions s.val, then you have violated the abstraction.
Type SmallSetOfInt is an abstract data type. That means its implementation can be changed at any time. The only thing that is guaranteed about it is that it will support the advertised operations.
I will compile your program with a modified definition of SmallSetOfInts. If you have violated the abstraction then your program will not compile with the modified intset.cpp and intset.h files.
Poor or missing contracts (up to 12 points). Every function must have a clear and precise contract. A contract tells what a function does, not how it works. A contract tells precisely how each parameter affects what the function does, and refers to parameters by name.
Functions do not do what they are supposed to do, or return incorrect results according to their contracts (up to 7 points per function). If a function is supposed to tell whether it made any progress, be sure that it truly does say whether it made progress. If you are sloppy you will find that you produce the wrong answer. In tactics 1 and 2, the function makes progress if it makes a change in any of the rows, columns or squares.
Tactics 1 and 2 are supposed to work on every row, column and square. Do not cut them off before they have done their jobs. For example, do not stop tactic 1 just because you made a change in the first row. Keep going until all of the rows, columns and squares have been done.
Crippled functions (up to 8 points). A crippled function is one that does not really do its job, but requires its caller to do part of the job.
Of particular concern is the function that reads a puzzle. Make sure that it does the whole job. You should not make mistakes, putting incorrect values into the puzzle, expecting to compensate for them later.
Poor indentation (up to 10 points). You are expected to indent your function definitions correctly. Minor problems will not lose much, but seriously bad indentation can lose 10 points.
Failure to explain tabs (2 points). Be sure that you either do not use tabs in your program or that you tell where your tab stops are at the top of the program. Different editors are set up with different tab stops. For example, write
// tab stops: every 4 charactersIf you do not know where the tab stops are, do not use tabs.
No debug prints (up to 6 points). There should be debug prints in tactics 1 and 2 showing the puzzle before and after they run, and showing what their return value is. There should also be debug prints in tactic 3, as indicated in the sketch. The debug prints should not print raw information. They should explain which function is printing them and what the information is.
Debug prints left on (up to 5 points). Turn off the debug prints. That is important, because with them turned on, your program does not do what it is required to do.
Awkward loop initialization (up to 2 points).
Initialization for a loop should be at the start of the
loop, not someplace else. This rule is particularly
important for nested loops. Look at the following.
j = 0;
k = 0;
while(j < 9)
{
while(k < 9)
{
...
}
}
The initialization of the loop that begins while(k < 9) is done
at the beginning of the outer loop. That means it is only done
the first time. To compensate for that, some programmers add
another initialization at the end of the inner loop, for the
next time around.
j = 0;
k = 0;
while(j < 9)
{
while(k < 9)
{
...
}
k = 0;
}
But this is really making a mistake and then compensating
for it, which is not the recommended way to write computer
programs. Instead, put the initialization for the inner
loop just before the inner loop, where it belongs.
j = 0;
while(j < 9)
{
k = 0;
while(k < 9)
{
...
}
}
Excessive inefficiency (up to 8 points). This program should solve a puzzle quickly. Each tactic should take the time to do its job, but it should not use excessively more time than is necessary.
Long lines, margin comments (up to 5 points) Avoid long lines. As a rule of thumb, keep lines at most 80 characters long. Avoid margin comments (comments written in the right margin telling what the code to the left of them does). Those comments rarely say anything useful, and they make lines long and clutter the program. If you need to write a comment inside a function, write a paragraph comment telling what the next paragraph of code does.
Kick-starting functions (up to 5 points). To use a function, just use it where its result is needed. Do not add an extra call. For example, do not write
while(tacticOne(p)) { tacticOne(p); ... }There is no need to get the program ready to call a function. If you already ran it, do not try to remind the compiler to run it. If you want to call a function once, call it once, not twice.
Packing functions together (up to 1 point). Put a blank line between function definitions. There is no need to pack them together. That makes the program more readable.
But also do not put huge blank sections in your program consisting of several blank lines in a row. A long space is two blank lines. Longer than that is excessive.
system("pause") (up to 5 points). Some programmers write system("pause") at the end of main to compensate for a flaw in the design of a Windows development environment. Since that is a nonstandard thing, I must edit your program to get rid of it before I can test your program. I should not have to fix your program for any reason.
You will turn in two versions of this program, unless your grade on version 1 is one that you are willing to keep. To submit a version of a program, run one of the following commands in a terminal window. I am assuming that your program is called sudoku.cpp. If it has a different name, substitute that name. Submit both your source program and your Makefile. Do not submit your executable program or test files (puzzle1.txt, etc.)
~abrahamsonk/3300/bin/submit 2a sudoku.cpp
~abrahamsonk/3300/bin/submit 2b sudoku.cpp