A Sudoku puzzle is a 9 ×ばつ 9 grid with some numbers between 1 and 9 in it, that has to be filled in so that
- every row,
- every column, and
- each of the nine 3 ×ばつ 3 blocks
contain all the numbers from 1 to 9.
For example the following matrix is not a valid solution of a Sudoku puzzle: the fourth column contains two 7's, block 4 contains also two 7's, and block 5 contains two 9's (there are more reasons why this matrix is not a valid solution but we will not list all them):
1 8 2 | 3 5 9 | 4 7 6 7 5 3 | 2 6 4 | 9 8 1 6 4 9 | 7 1 8 | 2 5 3 ------+-------+------ 2 6 4 | 9 7 3 | 8 1 5 9 1 7 | 4 8 5 | 9 3 2 8 3 5 | 7 2 1 | 7 4 9 ------+-------+------ 5 9 6 | 1 4 7 | 3 2 8 4 2 8 | 5 3 6 | 1 9 7 3 7 1 | 8 9 2 | 5 6 4
Write a program to determine if a given 9 ×ばつ 9 matrix is the solution of a Sudoku puzzle.
In the example above we have marked the limits of the 9 blocks to make the example easier to follow, but the delimiter characters do not appear in the input.
Important: the only structures I can use are struct
s and the defined in the vector
library. I cannot use class
es (as I have not studied them yet).
#include <iostream>
#include <vector>
//I know I should not include std
//but I am restricted to do so.
using namespace std;
const int sdk_size = 9;
const int sdk_blocks = 3;
typedef vector<int> permutation;
typedef vector<permutation> sdk;
sdk read_sdk()
{
sdk v(sdk_size, permutation(sdk_size));
for (int y = 0; y < sdk_size; ++y) {
for (int x = 0; x < sdk_size; ++x) {
cin >> v[y][x];
}
}
return v;
}
//Checks if a permutation *is* a permutation, i.e.
//it contains every number in 1..sdk_size once and only once.
bool is_permutation(const permutation& per)
{
vector<bool> used(sdk_size + 1, false);
for (int i = 0; i < sdk_size; ++i) {
if (per[i] < 1 or per[i] > sdk_size or used[per[i]]) {
return false;
}
used[per[i]] = true;
}
return true;
}
bool has_valid_rows(const sdk& v)
{
for (int y = 0; y < sdk_size; ++y) {
if (not is_permutation(v[y])) return false;
}
return true;
}
bool has_valid_cols(const sdk& v)
{
for (int x = 0; x < sdk_size; ++x) {
permutation col(sdk_size);
for (int y = 0; y < sdk_size; ++y) {
col[y] = v[y][x];
}
if (not is_permutation(col)) return false;
}
return true;
}
bool has_valid_blocks(const sdk& v)
{
//A block is a submatrice of v with start point (y0,x0)
//and height and width of sdk_size.
for (int y_0 = 0; y_0 < sdk_size; y_0 += sdk_blocks) {
for (int x_0 = 0; x_0 < sdk_size; x_0 += sdk_blocks) {
permutation block;
for (int dy = 0; dy < sdk_blocks; ++dy) {
for (int dx = 0; dx < sdk_blocks; ++dx) {
block.push_back(v[y_0+dy][x_0+dx]);
}
}
if (not is_permutation(block)) return false;
}
}
return true;
}
bool is_valid_sdk(const sdk& v)
{
if (not has_valid_rows(v)) return false;
if (not has_valid_cols(v)) return false;
if (not has_valid_blocks(v)) return false;
return true;
}
int main()
{
sdk v = read_sdk();
if (is_valid_sdk(v)) {
cout << "The sudoku is well-formed" << endl;
} else {
cout << "The sudoku is not well-formed" << endl;
}
}
7 Answers 7
I see some things that may help you improve your code.
Don't abuse using namespace std
Putting using namespace std
within your program is generally a bad habit that you'd do well to avoid.
The comment in the code seems to indicate you know this and can't change it for some reason, but others who may read this question and answer may benefit
Simplify your code
The current code has this function:
bool is_valid_sdk(const sdk& v)
{
if (not has_valid_rows(v)) return false;
if (not has_valid_cols(v)) return false;
if (not has_valid_blocks(v)) return false;
return true;
}
However, this could be considerably shorter and easier to read and understand if written like this:
bool is_valid_sdk(const sdk& v)
{
return has_valid_rows(v) and has_valid_cols(v) and has_valid_blocks(v);
}
Reconsider the design
Although the current code works, it's not very efficient in that it makes a copy of each vector in order to check it. An alternative approach would be to allow checking in place without making a copy. One way to do that would be to separate the iteration through the rows/columns/blocks into a separate function that could be passed to the is_permutation
function. For example:
bool is_permutation(const sdk& v, int x, int y,
int(*next)(const sdk& v, int& x, int& y))
{
vector<bool> used(sdk_size, false);
for (int i = 0; i < sdk_size; ++i) {
int val = next(v, x, y);
if (val < 1 or val > sdk_size or used[val-1]) {
return false;
}
used[val-1] = true;
}
return true;
}
int next_in_blk(const sdk& v, int &x, int &y)
{
int val = v[x][y];
if (++y % sdk_blocks == 0) {
y -= sdk_blocks;
++x;
}
return val;
}
bool has_valid_blocks(const sdk& v)
{
for (int i = 0; i < sdk_size; ++i) {
int x = sdk_blocks * (i / sdk_blocks);
int y = sdk_blocks * (i % sdk_blocks);
if (not is_permutation(v, x, y, next_in_blk)) return false;
}
return true;
}
The corresponding functions for row
and col
are even simpler.
-
4\$\begingroup\$ The alternate operators such as
and
are highly unusual. Why do you recommend them? \$\endgroup\$anon– anon2016年01月24日 20:56:24 +00:00Commented Jan 24, 2016 at 20:56 -
\$\begingroup\$ @isanae Most likely he used them as the OP was using them originally \$\endgroup\$RedLaser– RedLaser2016年01月24日 21:32:47 +00:00Commented Jan 24, 2016 at 21:32
-
\$\begingroup\$ @RedLaser That'll teach me not to pay attention. \$\endgroup\$anon– anon2016年01月24日 21:34:29 +00:00Commented Jan 24, 2016 at 21:34
-
2\$\begingroup\$ @isanae Yes, only because the OP used them. My own code never uses them because I started programming long before they were even an option. However, that's just my habit. There's nothing technically wrong with them and they are now part of the C++ standard. \$\endgroup\$Edward– Edward2016年01月24日 21:50:53 +00:00Commented Jan 24, 2016 at 21:50
-
1\$\begingroup\$ It's true that
vector<bool>
is packed, but it's appropriate in this case. For small values ofsdk_size
, as in this code, the difference will be so small as not to be noticeable, and for large values (say 1e6), the cache locality of a packed vector will be beneficial in both space and time efficiency. \$\endgroup\$Edward– Edward2016年01月25日 12:01:53 +00:00Commented Jan 25, 2016 at 12:01
On the overall, your code looks ok. Some points you could improve:
- Be more consistent with indentation (you use 2 spaces and 4 spaces inconsistently. I would suggest using 4 spaces everywhere).
- Be more consistent with spacing. Around the
+
operator, you sometimes use spaces and sometimes not. I would suggest always using spaces. - You should add error handling when reading data from
stdin
(the call tocin
can fail). - Try not to use abbreviations when not necessary; it lowers the readability of your code.
sdk
is typically used for "software development kit". I would suggest naming your functionreadSudoku
instead ofread_sdk
. - Send error messages to
cerr
, and normal log messages tocout
. For small programs this does not change a lot, but for big programs it is useful to be able to print errors only. - Do not write
if
conditions on a single line; this lowers readability: (if (...) return false
). It is easier to read when this is on two lines. - Do not use single letter names for parameters like
sdk v
. Now your code is very small so this does not change a lot, butv
does not have any meaning. Naming it e.g.grid
would be a lot better. - Do not use
and
,or
andnot
. Prefer&&
,||
and!
(the written versions of the operators exist only for backwards compatibility and historical reason, they should not be used in new code, see this).
-
\$\begingroup\$ About cerr and cout: I was also about to suggest this to the OP (because in general I do agree), but I think in this case, what is written to cout is the output of the program (even if it is in human readable way). \$\endgroup\$Attilio– Attilio2016年01月25日 14:59:57 +00:00Commented Jan 25, 2016 at 14:59
It's usually preferred to capitalize custom types, such as
Permutation
. This will help distinguish them from variables and functions.There's no need for this duplication:
if (not has_valid_rows(v)) return false; if (not has_valid_cols(v)) return false; if (not has_valid_blocks(v)) return false; return true;
Just use the
||
operator so that only onereturn false
is needed:if (!has_valid_rows(v) || !has_valid_cols(v) || !has_valid_blocks(v)) { return false; } return true;
(I've also replaced
not
with!
as mentioned in a comment.)For added protection and self-documentation, you can add
const
tov
's initialization.
-
4\$\begingroup\$ Or just
return !(!hvr || !hvc || !hvb)
(abbreviated the function calls, but you get it) \$\endgroup\$Kroltan– Kroltan2016年01月24日 18:14:06 +00:00Commented Jan 24, 2016 at 18:14 -
2\$\begingroup\$ Shorthand conditional returns are generally considered bad form due to poor legibility, but putting the
return true;
statement inside anelse
clause generally looks neater. \$\endgroup\$micheal65536– micheal655362016年01月24日 18:21:08 +00:00Commented Jan 24, 2016 at 18:21 -
\$\begingroup\$ @MichealJohnson: Are they? Where do you get that from? \$\endgroup\$Deduplicator– Deduplicator2016年01月25日 03:39:35 +00:00Commented Jan 25, 2016 at 3:39
-
\$\begingroup\$ I was going for a duplicate-free alternative, not necessarily the simplest one. This will at least avoid increasing the horizontal line count in case more functions are added. \$\endgroup\$Jamal– Jamal2016年01月25日 03:42:26 +00:00Commented Jan 25, 2016 at 3:42
-
4\$\begingroup\$ @Kroltan Or how about
return hvr && hvc && hvb
? No need to have unnecessary negation there. \$\endgroup\$Simon Forsberg– Simon Forsberg2016年01月25日 13:13:46 +00:00Commented Jan 25, 2016 at 13:13
Well, I really abhor
using namespace std;
, but you already know why it is bad, so we'll let that pass.Let's look at your function-names:
has_valid_rows has_valid_cols has_valid_blocks
According to the name, those functions answer the question:
Does the input have any valid rows/columns/blocks?Unfortunately, that's not the question they actually answer:
Are all the rows/columns/blocks valid?A better name would be
all_X_valid
or with negated outputhas_invalid_X
.Yes, C++ has
and
,or
,not
and the like. And you should know about them, because someone might use them. Still, consider abstaining, as they are uncommon, and don't actually buy you anything.You should axe
is_permutation
and do the test directly in the function.
Just the delegation has the same complexity (or more) than doing it directly on average, so it is a net loss according to any metric.That's certainly no less efficient than Edward's suggestion even if that should be inlined, and might be more maintainable.
You could explore
std::bitset
as the set's length is a compile-time constant.
Or just go with anunsigned
, and depend on that always having at least 16 value-bits.Your program does actually give a sensible output on input-failure, namely "The sudoku is not well-formed". But I'm not sure that's anything but serendipity.
Are you sure you shouldn't explicitly handle that?Don't use
std::endl
unless you might need an explicit flush.
Admittedly, doing that once, at the end of your program, doesn't really cost much performance or space, but it's a bad habit to get into.Regarding your constants:
Take a hard look at them, and think about how to generalize to bigger (or smaller, though that's boring) sudokus, and you will see that the only fundamental constant for a sudoku is length of a block.
Length of each side, count of different symbols, and blocksize, is blocklength squared.
Size of the whole sudoku is the product of the sides, or blocklength to the 4th power.
The program looks good to me, i.e. I cannot imagine cases for false positives or negatives.
I have only some minor remarks:
used
vector inis_permutation
is one element too big (in other words, its element at position 0 is never used); you could declare the vector of the right size, and always access element at indexper[i]] - 1
, instead ofper[i]]
.I suggest your main function does return a value, e.g. 0 on success, 1 on error.
I recommend always using brackets for
if
/else
/loops etc. statements, e.g. in places likeif (not is_permutation(v[y])) return false;
. In this way, you avoid confusion if you would like to add other statements to theif
/else
clause later.Although not a problem in any way, I had never seen the operator
not
used like this in C++ (I knew only about!
). However, I just discovered that operatornot
does, in fact, exist. I learnt something new today as well.
EDIT: As pointed out in comments: it is not a good practice to use the verbose form of the operators (e.g. and
instead of &&
) since they are provided only for compatibility with old keyboards, and virtually never used in a productive environment.
-
\$\begingroup\$ I might disagree that using an unfamiliar keyword with a far more common alternative exists is "not a problem in any way". \$\endgroup\$nhgrif– nhgrif2016年01月24日 16:26:02 +00:00Commented Jan 24, 2016 at 16:26
-
\$\begingroup\$ First of all, I appreciate your comments. I will take them into account. On the other hand, in Programming class we were encouraged to use
not
,or
andand
instead of!
,||
and&&
, as they "improve legibility". Are not those keywords common in the "real world", but in learning terms? \$\endgroup\$JnxF– JnxF2016年01月24日 16:29:25 +00:00Commented Jan 24, 2016 at 16:29 -
4\$\begingroup\$ "not", "or", and "and" are only provided for backwards compatibility, in order to support extremely old keyboards, e.g. not having the sign "|". This is an extremely bad suggestion to use them, they are almost never used in real code. \$\endgroup\$Étienne– Étienne2016年01月24日 16:47:44 +00:00Commented Jan 24, 2016 at 16:47
-
\$\begingroup\$ @Étienne: You are right, what you are saying are in fact good reason, not to use those keywords. I will edit my answer accordingly. \$\endgroup\$Attilio– Attilio2016年01月25日 15:02:00 +00:00Commented Jan 25, 2016 at 15:02
Simplification
Every time you use is_permutation
you negate it so I suggest you change it to:
bool is_not_permutation(const permutation& per)
{
vector<bool> used(sdk_size + 1, false);
for (int i = 0; i < sdk_size; ++i) {
if (per[i] < 1 or per[i] > sdk_size or used[per[i]]) {
return true;
}
used[per[i]] = true;
}
return false;
}
Which effectively negates the output at the function instead of at function reference.
-
4\$\begingroup\$ I don't consider this a good idea. It's more natural to look for
is_something
and negate it than to look foris_not_something
. Even the code length is almost identical in the end. \$\endgroup\$yo'– yo'2016年01月25日 10:58:37 +00:00Commented Jan 25, 2016 at 10:58
I think the best and easy way to validate a Sudoku grid is by constructing a new vector for row, column, square separately from the main matrix like this:
bool checkSudokuStatus(const std::vector<std::vector<int>>& grid)
{
for (int i = 0; i < SudokuSize; i++)
{
std::vector<int> row(SudokuSize);
std::vector<int> square(SudokuSize);
std::vector<int> column(SudokuSize);
for (int j = 0; j < SudokuSize; j++)
{
row[j] = grid[j][i];
column[j] = grid[i][j];
square[j] = grid[(i / BlockSize) * BlockSize + j / BlockSize][i * BlockSize % SudokuSize + j % BlockSize];
}
if (!(validate(column) && validate(row) && validate(square)))
return false;
}
return true;
}
In order to validate each vector, simply sort it just to check if it is equal to the index counter. If you are allowed to use STL <algorithm>
functions like std::sort
, it would be very easy to implement it like this:
bool validate(std::vector<int> check)
{
int i = 0;
std::sort(check.begin(), check.end());
for (int number : check)
{
if (number != ++i)
return false;
}
return true;
}
Putting the code together:
#include <iostream>
#include <vector>
#include <algorithm>
constexpr unsigned SudokuSize = 9;
constexpr unsigned BlockSize = 3;
bool validate(std::vector<int> check)
{
int i = 0;
std::sort(check.begin(), check.end());
for (int number : check)
{
if (number != ++i)
return false;
}
return true;
}
bool checkSudokuStatus(const std::vector<std::vector<int>>& grid)
{
for (int i = 0; i < SudokuSize; i++)
{
std::vector<int> row(SudokuSize);
std::vector<int> square(SudokuSize);
std::vector<int> column(SudokuSize);
for (int j = 0; j < SudokuSize; j++)
{
row[j] = grid[j][i];
column[j] = grid[i][j];
square[j] = grid[(i / BlockSize) * BlockSize + j / BlockSize][i * BlockSize % SudokuSize + j % BlockSize];
}
if (!(validate(column) && validate(row) && validate(square)))
return false;
}
return true;
}
int main()
{
std::vector<std::vector<int>> v
{
{1, 8, 2, 3, 5, 9, 4, 7, 6},
{7, 5, 3, 2, 6, 4, 9, 8, 1},
{6, 4, 9, 7, 1, 8, 2, 5, 3},
{2, 6, 4, 9, 7, 3, 8, 1, 5},
{9, 1, 7, 4, 8, 5, 6, 3, 2},
{8, 3, 5, 6, 2, 1, 7, 4, 9},
{5, 9, 6, 1, 4, 7, 3, 2, 8},
{4, 2, 8, 5, 3, 6, 1, 9, 7},
{3, 7, 1, 8, 9, 2, 5, 6, 4}
};
if (checkSudokuStatus(v))
std::cout << "The sudoku is well-formed" << std::endl;
else
std::cout << "The sudoku is not well-formed" << std::endl;
}
struct
can be every bit as powerful as aclass
. \$\endgroup\$std::vector
is aclass
. Andstruct
is implemented pretty much the same asclass
. \$\endgroup\$std::vector
is certainly aclass
- just a templated one. Astruct
and aclass
are functionally the same things in C++ - the only difference being default access of members (and inheritance) beingprivate
for aclass
andpublic
for astruct
. \$\endgroup\$