This is a simple program that solves a given Sudoku puzzle recursively. The input is provided as a file that contains the cells, 0 if empty, delimited by a ,
character.
#include <iostream>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <utility>
#include <exception>
#include <fstream>
#include <cctype>
#include <algorithm>
using std::cout;
using std::cin;
using std::endl;
using std::vector;
using std::string;
using std::string_view;
using std::pair;
using std::optional;
using puzzle_t = vector<vector<int>>;
using coord = pair<size_t, size_t>;
std::ostream& operator<<(std::ostream& other, const puzzle_t& puzzle) {
for (size_t i = 0; i < 9; i++) {
char comma[3] = {'\0', ' ', '\0'};
for (size_t j = 0; j < 9; j++) {
other << comma << puzzle[i][j];
comma[0] = ',';
}
other << endl;
}
return other;
}
vector<string> split(const string& str, const string& delim) {
vector<string> tokens;
string_view str_view = str;
size_t pos = 0;
do {
pos = str_view.find(delim);
tokens.emplace_back(str_view.substr(0, pos));
str_view = str_view.substr(pos + 1, str_view.size());
} while (pos != string_view::npos);
return tokens;
}
bool safe_isspace(char c) {
return std::isspace(static_cast<unsigned char>(c));
}
string strip(const string& str) {
size_t i;
string result = str;
for (i = 0; i < result.size() && safe_isspace(result[i]); i++);
result = result.substr(i, result.length());
for (i = result.length() - 1; i >= 0 && safe_isspace(result[i]); i--);
result = result.substr(0, i + 1);
return result;
}
template <class T>
vector<T> filter_empty(vector<T>&& vec) {
vector<T> result;
for (auto& t : vec) {
if (!t.empty()) {
result.push_back(t);
}
}
return result;
}
optional<puzzle_t> read_puzzle(std::istream& in) {
try {
puzzle_t puzzle;
while (puzzle.size() < 9) {
string line;
std::getline(in, line);
if (line.empty()) {
continue;
}
vector<string> all_cells = split(line, ",");
vector<int> row;
for (auto& str : all_cells) {
str = strip(str);
}
all_cells = filter_empty(std::move(all_cells));
if (all_cells.size() != 9) {
return { };
}
for (auto& str : all_cells) {
int value = std::stoi(str);
if (value < 0 || value > 9) {
return { };
}
row.push_back(value);
}
puzzle.push_back(row);
}
return { puzzle };
} catch (const std::exception& exn) {
return { };
}
}
bool validate_puzzle(const puzzle_t& puzzle ) {
auto validate_group = [](vector<int> group) -> bool {
for (int i = 1; i <= 9; i++) {
if (std::count(group.begin(), group.end(), i) > 1) {
return false;
}
}
return true;
};
auto get_col = [&puzzle](size_t col_n) -> vector<int> {
vector<int> col(9);
for (const auto& row : puzzle) {
col.push_back(row[col_n]);
}
return col;
};
auto get_group = [&puzzle](size_t i, size_t j) -> vector<int> {
vector<int> group;
for (int row = i * 3; row < i * 3 + 3; row++) {
for (int col = j * 3; col < j * 3 + 3; col++) {
group.push_back(puzzle[row][col]);
}
}
return group;
};
return validate_group(puzzle[0]) &&
validate_group(puzzle[1]) &&
validate_group(puzzle[2]) &&
validate_group(puzzle[3]) &&
validate_group(puzzle[4]) &&
validate_group(puzzle[5]) &&
validate_group(puzzle[6]) &&
validate_group(puzzle[7]) &&
validate_group(puzzle[8]) &&
validate_group(get_col(0)) &&
validate_group(get_col(1)) &&
validate_group(get_col(2)) &&
validate_group(get_col(3)) &&
validate_group(get_col(4)) &&
validate_group(get_col(5)) &&
validate_group(get_col(6)) &&
validate_group(get_col(7)) &&
validate_group(get_col(8)) &&
validate_group(get_group(0, 0)) &&
validate_group(get_group(0, 1)) &&
validate_group(get_group(0, 2)) &&
validate_group(get_group(1, 0)) &&
validate_group(get_group(1, 1)) &&
validate_group(get_group(1, 2)) &&
validate_group(get_group(2, 0)) &&
validate_group(get_group(2, 1)) &&
validate_group(get_group(2, 2));
}
optional<coord> get_next_empty_coord(const puzzle_t& puzzle) {
for (size_t i = 0; i < 9; i++) {
for (size_t j = 0; j < 9; j++) {
if (puzzle[i][j] == 0) {
return { std::make_pair(i, j) };
}
}
}
return { };
}
void solve_recursive(puzzle_t& puzzle, vector<puzzle_t>& results) {
auto next_coord_opt = get_next_empty_coord(puzzle);
if (!next_coord_opt) {
results.push_back(puzzle);
return;
}
auto next_coord = next_coord_opt.value();
for (int i = 1; i <= 9; i++) {
puzzle[next_coord.first][next_coord.second] = i;
if (validate_puzzle(puzzle)) {
solve_recursive(puzzle, results);
}
puzzle[next_coord.first][next_coord.second] = 0;
}
}
vector<puzzle_t> solve(puzzle_t&& puzzle) {
vector<puzzle_t> result;
solve_recursive(puzzle, result);
return result;
}
bool solver(const string& in_path, const string& out_path) {
std::ifstream fin(in_path);
std::ofstream fout(out_path);
if (!fin || !fout) {
cout << "IO failure" << endl;
return false;
}
optional<puzzle_t> puzzle = read_puzzle(fin);
if (!puzzle || puzzle.value().size() != 9 || puzzle.value()[0].size() != 9) {
cout << "Invalid puzzle" << endl;
return false;
}
auto solutions = solve(std::move(puzzle.value()));
fout << "Number of solutions found: " << solutions.size() << endl;
fout << endl;
for (const auto& solution : solutions) {
fout << solution << endl;
fout << endl;
}
fin.close();
fout.close();
return true;
}
int main(int argc, char* argv[]) {
std::ios::sync_with_stdio(false);
if (argc == 3) {
string in_path = argv[1];
string out_path = argv[2];
if (solver(in_path, out_path)) {
return EXIT_SUCCESS;
} else {
return EXIT_FAILURE;
}
} else {
cout << "Invalid number of arguments to program." << endl;
return EXIT_FAILURE;
}
}
The program is compiled with clang using the following options.
$ clang++ -std=c++17 -Wall -Werror -O3 -o sudoku_solver sudoku_solver.cpp # Normal
$ clang++ -std=c++17 -Wall -Werror -O0 -g -fsanitize=address -fsanitize=undefined -fno-optimize-sibling-calls -o sudoku_solver sudoku_solver.cpp # for debug with ASAN
The program can be run as follows.
$ sudoku_solver <input_file> <output_file>
Here is an example of the program running. Here is an example input. Note, that whitespace is added for human readability, but the program is expected to ignore empty lines.
0, 0, 5, 0, 8, 0, 3, 0, 2
4, 2, 0, 0, 0, 0, 5, 0, 0
6, 0, 0, 0, 0, 4, 0, 0, 0
0, 0, 0, 3, 0, 0, 9, 0, 0
3, 0, 0, 0, 2, 6, 0, 0, 0
0, 0, 0, 0, 0, 0, 0, 7, 0
0, 0, 0, 0, 7, 0, 6, 8, 0
0, 9, 8, 0, 0, 0, 0, 0, 4
0, 0, 0, 5, 0, 0, 0, 0, 0
Here is the output generated by the program.
Number of solutions found: 1
9, 1, 5, 6, 8, 7, 3, 4, 2
4, 2, 7, 9, 1, 3, 5, 6, 8
6, 8, 3, 2, 5, 4, 1, 9, 7
8, 7, 1, 3, 4, 5, 9, 2, 6
3, 4, 9, 7, 2, 6, 8, 5, 1
2, 5, 6, 8, 9, 1, 4, 7, 3
1, 3, 2, 4, 7, 9, 6, 8, 5
5, 9, 8, 1, 6, 2, 7, 3, 4
7, 6, 4, 5, 3, 8, 2, 1, 9
std::
size_t
. ;) \$\endgroup\$9
in a lot of places? \$\endgroup\$