Examples:
/* Definition of the NoEights class from Topcoder SRM 355, D2, 550 pointer. */
class NoEights {
public:
int smallestAmount(int low, int high);
};
|
I have a driver program in src/no_eights_main.cpp. If you give it 0, 1, 2 or 3 on the command line, it will do that example above. If you give it "-", then it will read low and high from standard input.
The makefile compiles src/no_eights_main.cpp with src/no_eights.cpp to make bin/no_eights:
UNIX> make clean rm -f a.out obj/* bin/* UNIX> make bin/no_eights g++ -std=c++98 -O3 -Wall -Wextra -Iinclude -c -o obj/no_eights_main.o src/no_eights_main.cpp g++ -std=c++98 -O3 -Wall -Wextra -Iinclude -c -o obj/no_eights.o src/no_eights.cpp g++ -std=c++98 -O3 -Wall -Wextra -Iinclude -o bin/no_eights obj/no_eights_main.o obj/no_eights.o UNIX>
The key insight is to look at l and h as strings with equal numbers of digits. Then, the common prefixes of l and h allow us to determine the minimum number of eights. The problem description says that h will be at most a 10-digit number, so simply convert both to 10-digit strings that represent the numbers with leading zeros.
In other words, if l equals 8 and h equals 20, then convert l to "0000000008" and h to "0000000020".
Now, look at the first digit of both numbers. Call them h[0] and l[0]. If both equal '8', then every number between h and l has to start with '8'. If we remove the '8' from both strings and solve the problem recursively, then our answer is one plus the answer of the recursive problem.
Instead, suppose they both do not equal '8', but they do equal each other. If we remove the digit from both numbers and solve it recursively, then we have the answer.
Suppose they do not equal each other, and h[0] does not equal '8'. Then, you know the number beginning with h[0] and having zero's in every other digit is between l and h, and it has zero 8's. You can return zero.
Suppose they do not equal each other, and h[0] equals '8'. Then, you know the number beginning with l[0] and having nines in every other digit is between l and h, and it has zero 8's. You can return zero again.
This maps itself to a straightforward solution, in src/no_eights.cpp:
int NE(const string &l, const string &h, size_t index)
{
if (index == l.size()) return 0; // Base case when we have no digits left.
if (l[index] != h[index]) return 0;
if (l[index] == '8') return 1 + (NE(l, h, index+1));
return 0 + (NE(l, h, index+1));
}
int NoEights::smallestAmount(int low, int high)
{
char b[20];
string l, h;
snprintf(b, 20, "%010d", low); // Conversion to 10 digit strings with leading zeros
l = b;
snprintf(b, 20, "%010d", high);
h = b;
return NE(l, h, 0);
}
|
Now, you could have solved that with a for loop, but sometimes it's easier to think recursively. What's the running time? It's O(n), where n is the length of the string.
UNIX> bin/no_eights 0 0 UNIX> bin/no_eights 1 2 UNIX> bin/no_eights 2 1 UNIX> bin/no_eights 3 2 UNIX> echo 80888 80899 | bin/no_eights - 2 UNIX>
We are given six numbers: G1, S1 and B1, representing the number of gold, silver and bronze coins that we currently have, and G2, S2 and B2, representing the number of gold, silver and bronze coins that we want to have. All are limited by 1,000,000. We have exchange rates from the bank:
Example G1 S1 B1 G2 S2 B2 Answer 0 1 0 0 0 0 81 10: One gold to 9 silver. 9 silver to 81 bronze. 1 1 100 12 5 53 33 7: 44 silver to 4 gold. 3 silver to 27 bronze. 2 1 100 12 5 63 33 -1: Impossible. 3 5 10 12 3 7 9 0: Got already.
/* Header file for Topcoder SRM 351, D1, 250-Pointer: CoinsExchange */
#include <string>
class CoinsExchange {
public:
int countExchanges(int G1, int S1, int B1, int G2, int S2, int B2);
protected: /* I've added this variable to help print out the state. */
std::string nest;
};
|
And the driver is src/coins_exchange_main.cpp. You can give it the coins on standard input if you give it a dash on the command line. Otherwise, you can give it example numbers on the command line.
int CoinsExchange::countExchanges(int G1, int S1, int B1, int G2, int S2, int B2)
{
int rv;
rv = CE2(G2-G1, S2-S1, B2-B1);
return (rv >= 0) ? rv : -1;
}
|
Now, to implement CE2, here are my series of yes/no questions. I'm going to walk through them one by one:
If the answer to this is yes, then the only way we can get gold is from silver. So we make a recursive call where we set gold to 0, and add G*11 to our silver needs. If that is successful, then we'll be able to do G exchanges and get our gold. Therefore, we add G to the answer of the recursive call and return.
Here's what that looks like in the code:
if (G > 0) return G + CE2(0, S+G*11, B); |
You'll note that since we return after the recursion, we don't get past this point in the code unless we have enough gold. That simplifies the rest of the code.
If the answer to this is yes, then the only way we can get bronze is from silver. So, similar to gold, we make a recursive call that adds to the silver needs, and then we add the number of silver that we convert to the result to account for the exchanges of silver to bronze. Again, the code is simple. (B+8)/9 is the number of silver we need to convert to B bronze.
if (B > 0) return (B+8)/9 + CE2(G, S+(B+8)/9, 0); |
If we've reached this point in the program, we do not need gold or silver. Why? Because those cases were handled above, and we've returned already. So, if we don't need silver, we are done, and return 0.
if (S <= 0) return 0; |
If the answer to this is yes, then we should make those exchanges, and we're done. Here's the code:
if (-(G*9) >= S) return (S+8)/9; |
If we've reached this point, we cannot satisfy all of our silver with gold, so we need to exhange all of our code. Keeping with how we're solving this problem, we do that by subtracting the exhanged gold from our silver needs, making a recursive call and returning:
if (G < 0) return -G + CE2(0, S-(-G*9), B); |
At this point in the program, we don't have any gold, so if we're going to get silver, it has to be from bronze. If that works, we return the number of exchanges:
if (-B >= S*11) return S; |
Now we have no gold and we don't have enough bronze. We have failed. I return -1000000000, because that way, whatever we return to the caller, it will be a negative number. You may have to think about that a bit -- it's a nice use of a sentinel, but like all sentinels, it can be a little confusing until you see how it works overall.
return -1000000000; |
We'll build a solution. First have to read a problem in -- I'll do that from standard input -- numbers are '1' through '9', empty cells are '-' and everything else is ignored. I store a puzzle in a vector of nine strings, each with nine characters. I do this in a Read() method of a class called Sudoku, and I also implement a Print() method in src/sudoku1.cpp:
class Sudoku {
public:
void Read(); // Read from standard input
void Print() const; // Print to standard output
protected:
vector <string> puzzle; // Hold the puzzle in a vector of 9 strings
};
void Sudoku::Read()
{
int i, j;
char c;
puzzle.clear();
puzzle.resize(9);
for (i = 0; i < 9; i++) { // Read the puzzle, error checking.
for (j = 0; j < 9; j++) {
do {
if (!(cin >> c)) {
cerr << "Not enough cells.\n";
exit(1);
}
} while (isspace(c));
if (c != '-' && (c < '1' || c > '9')) {
cerr << "Bad character " << c << endl;
exit(1);
}
puzzle[i].push_back(c);
}
}
}
|
void Sudoku::Print() const
{
int i, j;
for (i = 0; i < puzzle.size(); i++) {
for (j = 0; j < puzzle[i].size(); j++) {
cout << puzzle[i][j];
if (j == 2 || j == 5) cout << " ";
}
cout << endl;
if (i == 2 || i == 5) cout << endl;
}
}
int main()
{
Sudoku S;
S.Read();
S.Print();
}
|
I have the example from the Wikipedia page in two files: txt/sudex1.txt and txt/sudex2.txt. They differ in the amount of whitespace. However, when the program reads them in, they produce the same output:
UNIX> make bin/sudoku1 g++ -std=c++98 -O3 -o bin/sudoku1 src/sudoku1.cpp UNIX> cat txt/sudex1.txt 53--7---- 6--195--- -98----6- 8---6---3 4--8-3--1 7---2---6 -6----28- ---419--5 ----8--79 UNIX> bin/sudoku1 < txt/sudex1.txt 53- -7- --- 6-- 195 --- -98 --- -6- 8-- -6- --3 4-- 8-3 --1 7-- -2- --6 -6- --- 28- --- 419 --5 --- -8- -79 UNIX> |
UNIX> cat txt/sudex2.txt 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 UNIX> bin/sudoku1 < txt/sudex2.txt 53- -7- --- 6-- 195 --- -98 --- -6- 8-- -6- --3 4-- 8-3 --1 7-- -2- --6 -6- --- 28- --- 419 --5 --- -8- -79 UNIX> |
As a next step, we implement methods to check whether rows, columns or panels are valid. They are straightforward. In src/sudoku2.cpp, we check to see whether the input matrix is indeed valid.
In class, I pause here and ask you to write the row_ok() method. Read this page for a discussion of various bad ways to write row_ok().
class Sudoku {
public:
void Read(); // Read from standard input
void Print() const; // Print to standard output
int row_ok(int r) const; // Test row r for correctness
int column_ok(int c) const; // Test cols r for correctness
int panel_ok(int pr, int pc) const; // Test panel pr/pc (both 0,1,2) for correctness
protected:
vector <string> puzzle; // Hold the puzzle in a vector of 9 strings
};
int Sudoku::row_ok(int r) const
{
vector <int> checker; /* Use this to make sure no digit is set twice. */
int c;
checker.clear();
checker.resize(10, 0);
for (c = 0; c < 9; c++) {
if (puzzle[r][c] != '-') {
if (checker[puzzle[r][c]-'0']) return 0;
checker[puzzle[r][c]-'0'] = 1;
}
}
return 1;
}
int Sudoku::column_ok(int c) const
{
vector <int> checker;
int r;
checker.resize(10, 0);
for (r = 0; r < 9; r++) {
if (puzzle[r][c] != '-') {
if (checker[puzzle[r][c]-'0']) return 0;
checker[puzzle[r][c]-'0'] = 1;
}
}
return 1;
}
int Sudoku::panel_ok(int pr, int pc) const
{
vector <int> checker;
int r, c;
int i, j;
checker.resize(10, 0);
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
r = pr*3+i;
c = pc*3+j;
if (puzzle[r][c] != '-') {
if (checker[puzzle[r][c]-'0']) return 0;
checker[puzzle[r][c]-'0'] = 1;
}
}
}
return 1;
}
int main()
{
int r, c;
Sudoku S;
S.Read();
for (r = 0; r < 9; r++) if (!S.row_ok(r)) printf("Bad row %d\n", r);
for (c = 0; c < 9; c++) if (!S.column_ok(c)) printf("Bad col %d\n", c);
for (r = 0; r < 3; r++) for (c = 0; c < 3; c++) {
if (!S.panel_ok(r, c)) printf("Bad panel %d %d\n", r, c);
}
}
|
I have some example puzzles (txt/sudex3.txt, txt/sudex4.txt & txt/sudex5.txt) with errors: the program correctly identifies them:
UNIX> make bin/sudoku2 g++ -std=c++98 -O3 -o bin/sudoku2 src/sudoku2.cpp UNIX> bin/sudoku2 < txt/sudex3.txt Bad row 3 UNIX> bin/sudoku2 < txt/sudex4.txt Bad col 7 UNIX> bin/sudoku2 < txt/sudex5.txt Bad panel 1 2 UNIX>Now, this gives us all the pieces to write a really brain-dead recursive solver. What it does is the following:
class Sudoku {
public:
void Read(); // Read from standard input
void Print() const; // Print to standard output
void Solve(); // Solve the problem
int row_ok(int r) const; // Test row r for correctness
int column_ok(int c) const; // Test cols r for correctness
int panel_ok(int pr, int pc) const; // Test panel pr/pc (both 0,1,2) for correctness
protected:
vector <string> puzzle; // Hold the puzzle in a vector of 9 strings
};
void Sudoku::Solve()
{
int r, c, i;
for (r = 0; r < 9; r++) {
for (c = 0; c < 9; c++) {
if (puzzle[r][c] == '-') { /* Find the first empty cell. */
for (i = '1'; i <= '9'; i++) { /* Try every digit. */
puzzle[r][c] = i; /* If the digit is legal, call Solve() recursively */
if (row_ok(r) && column_ok(c) && panel_ok(r/3, c/3)) Solve();
}
puzzle[r][c] = '-';
return;
}
}
}
Print(); /* If we get here, the puzzle has been solved. */
exit(0);
}
|
It works on our example, pretty quickly (at this point, I'm assuming that you have made all of the executables).
UNIX> time bin/sudoku3 < txt/sudex1.txt 534 678 912 672 195 348 198 342 567 859 761 423 426 853 791 713 924 856 961 537 284 287 419 635 345 286 179 0.043u 0.001s 0:00.04 100.0% 0+0k 0+0io 0pf+0w UNIX>I find that a little depressing, actually, that a program that brain-dead can solve a puzzle in seconds that may take me 10+ minutes of logic and head-scratching.
However, if you're like me, it seems like we could speed this up. Let's explore.
UNIX> g++ -O3 -o bin/sudoku3 src/sudoku3.cpp UNIX> time sh -c 'for i in 1 2 3 4 5 6 ; do bin/sudoku3 < txt/test_puzzle_$i.txt > /dev/null; done' 0.430u 0.000s 0:00.42 102.3% 0+0k 0+0io 0pf+0w UNIX>Roughly 0.07 seconds for each test. What's one easy way to speed this up? Well, it seems a bit inefficient to look for a blank space from the beginning each time we call Solve(). Let's instead parameterize Solve() with the row and column, and then when we call it recursively, we give it the next cell. The updated Solve() is in src/sudoku4.cpp:
void Sudoku::Solve(int r, int c)
{
int i;
if (c == 9) { c = 0; r++; }
while (r < 9) {
if (puzzle[r][c] == '-') {
for (i = '1'; i <= '9'; i++) {
puzzle[r][c] = i;
if (row_ok(r) && column_ok(c) && panel_ok(r/3, c/3)) Solve(r, c+1);
}
puzzle[r][c] = '-';
return;
}
c++;
if (c == 9) { c = 0; r++; }
}
Print();
exit(0);
}
|
We first call it with Solve(0, 0). Does it speed things up? A little:
UNIX> time sh -c 'for i in 1 2 3 4 5 6 ; do bin/sudoku4 < txt/test_puzzle_$i.txt > /dev/null; done' 0.420u 0.000s 0:00.40 105.0% 0+0k 0+0io 0pf+0w UNIX>I'm surprised that it doesn't speed matters up more. Whatever. Let's try something more drastic. For each row, column and panel, let's keep a set of valid numbers that can be entered. Then, we have two potential speed-ups. First, when we want to test an empty cell, we can traverse the legal values for the cell's row, then test the column & panel sets to see if the value is legal for those too. If so, we can call Solve() recursively. That eliminates the calls to row_ok(), column_ok() and panel_ok().
The code is a bit icky -- it's in src/sudoku5.cpp. First, here's the updated class definition:
typedef set <int> ISet;
typedef vector <ISet> VISet;
class Sudoku {
public:
vector <string> puzzle; // Hold the puzzle in a vector of 9 strings
void Read(); // Read from standard input
void Print() const; // Print to standard output
void Solve(int r, int c); // Solve starting at the given row/col
int row_ok(int r) const; // Test row r for correctness
int column_ok(int c) const; // Test cols r for correctness
int panel_ok(int pr, int pc) const; // Test panel pr/pc (both 0,1,2) for correctness
vector <ISet> vrows; // Sets of legal values for each row.
vector <ISet> vcols; // Sets of legal values for each row.
vector <VISet> vpanels; // Sets of legal values for each panel.
};
|
And here's Solve():
void Sudoku::Solve(int r, int c)
{
int i, j, e;
vector <int> to_try;
ISet::iterator rit, cit, pit;
/* At the beginning, first put all values into the three vectors of sets: */
if (r == 0 && c == 0) {
vrows.resize(9);
vcols.resize(9);
vpanels.resize(3);
for (i = 0; i < 3; i++) vpanels[i].resize(3);
for (i = 0; i < 9; i++) {
for (j = '1'; j <= '9'; j++) {
vrows[i].insert(j);
vcols[i].insert(j);
vpanels[i/3][i%3].insert(j);
}
}
/* Then, run through each row, column and panel of the puzzle,
and remove values from the sets. */
for (i = 0; i < 9; i++) {
for (j = 0; j < 9; j++) {
if (puzzle[i][j] != '-') {
e = puzzle[i][j];
vrows[i].erase(vrows[i].find(e));
vcols[j].erase(vcols[j].find(e));
vpanels[i/3][j/3].erase(vpanels[i/3][j/3].find(e));
}
}
}
}
if (c == 9) { c = 0; r++; }
/* Now, instead of trying every value and testing for legality, we instead create
a vector from all of the legal values in the row. We traverse that vector, and
if a value is legal in the column and panel, then we add it to the puzzle and
remove it from the three sets. Then we make the recursive call, and add the value
back to the sets. This code is kind of a pain, isn't it? */
while (r < 9) {
if (puzzle[r][c] == '-') {
for(rit = vrows[r].begin(); rit != vrows[r].end(); rit++) to_try.push_back(*rit);
for (i = 0; i < (int) to_try.size(); i++) {
e = to_try[i];
cit = vcols[c].find(e);
if (cit != vcols[c].end()) {
pit = vpanels[r/3][c/3].find(e);
if (pit != vpanels[r/3][c/3].end()) {
rit = vrows[r].find(e);
vrows[r].erase(rit);
vcols[c].erase(cit);
vpanels[r/3][c/3].erase(pit);
puzzle[r][c] = e;
Solve(r, c+1);
vrows[r].insert(e);
vcols[c].insert(e);
vpanels[r/3][c/3].insert(e);
}
}
}
puzzle[r][c] = '-';
return;
}
c++;
if (c == 9) { c = 0; r++; }
}
Print();
exit(0);
}
|
Although a bit spindly, the code is straightforward. The only subtlety that I see is using to_try. Why did I do this? Why didn't I simply use rit to traverse vrows[r]? The reason is that I potentially erase rit inside the loop -- once I do that, I invalidate rit, which would be problematic inside a for loop that uses rit. Yes, I could store rit++ and change the loop -- that's probably faster; however, using to_try doesn't seem like a bad alternative.
Is it faster? Let's see:
UNIX> time sh -c 'for i in 1 2 3 4 5 6 ; do bin/sudoku5 < txt/test_puzzle_$i.txt > /dev/null; done' 0.210u 0.010s 0:00.18 122.2% 0+0k 0+0io 0pf+0w UNIX>Well, it runs in 50 percent of the time of sudoku4, so I guess I should be happy. I'm not really, but I'll pretend. There are lots of things to try -- for example, src/sudoku6.cpp creates to_try from the smallest of vrows[r], vcols[c] and vpanels[r/3][c/3]:
UNIX> time sh -c 'for i in 1 2 3 4 5 6 ; do bin/sudoku6 < txt/test_puzzle_$i.txt > /dev/null; done' 0.190u 0.000s 0:00.17 111.7% 0+0k 0+0io 0pf+0w UNIX>Let's try something else....
As illustrated in those lectures, you can use integers and bit operations to represent sets. This actually simplifies the code above. In src/sudoku7.cpp, we do this. (BTW, this code and the code in src/sudoku8.cpp are of an older style, and a little different than the code above -- you shouldn't have a hard time navigating them).
Actually, we do quite a bit more. First, we use the numbers 1 through 9 in the puzzle rather than their characters. We represent '-' with 0. The sets are now vectors of integers rather than sets. We also have the panel set be a flat vector of nine elements. We use the procedure rctoindex() to convert row and column indices to a single index for this vector.
Now, to create the initial sets for rows, columns and panels, we do the following: We first create sets of the numbers that are in each row/column/panel, and then we take their complement so that we have sets of the numbers that are not in each row/column/panel:
// In the constructor:
RS.resize(9, 0);
CS.resize(9, 0);
PS.resize(9, 0);
for (i = 0; i < 9; i++) {
for (j = 0; j < 9; j++) {
if (P[i][j] != 0) {
RS[i] |= (1 << P[i][j]);
CS[j] |= (1 << P[i][j]);
PS[rctoindex(i, j)] |= (1 << P[i][j]);
}
}
}
for (i = 0; i < 9; i++) {
RS[i] = ~RS[i];
CS[i] = ~CS[i];
PS[i] = ~PS[i];
}
}
|
Then the solver takes the intersection of the three sets, and only puts elements that are in that intersection into the recursive tester:
int Sudoku::Solve(int r, int c)
{
int i, j;
while (r < 9) {
while (c < 9) {
if (P[r][c] == 0) {
j = (RS[r] & CS[c] & PS[rctoindex(r, c)]); // J is the intersection of the three sets
for (i = 1; i <= 9; i++) {
if (j & (1 << i)) {
P[r][c] = i;
RS[r] &= (~(1 << i)); // Remove bit i from RS, CS and PS
CS[c] &= (~(1 << i));
PS[rctoindex(r, c)] &= (~(1 << i));
if (Solve(r, c)) return 1;
RS[r] |= (1 << i); // Put bit i back into RS, CS and PS
CS[c] |= (1 << i);
PS[rctoindex(r, c)] |= (1 << i);
}
}
P[r][c] = 0;
return 0;
}
c++;
}
if (c == 9) { r++; c = 0; }
}
return 1;
}
|
Now we're talking speed improvements!
UNIX> g++ -O3 -o bin/sudoku7 src/sudoku7.cpp UNIX> time sh -c 'for i in 1 2 3 4 5 6 ; do bin/sudoku7 < txt/test_puzzle_$i.txt > /dev/null; done' 0.070u 0.000s 0:00.02 350.0% 0+0k 0+0io 0pf+0w UNIX>The improvement comes from the following reason -- for small sets, bit operations are much faster than using balanced binary trees (which is how the STL implements sets).