制作一个数独生成器并用C ++检查方块

时间:2018-05-23 13:12:55

标签: c++ sudoku

我试图在生成数独网格时检查平方有效性。 (如果删除isSquarevalid())代码将正常工作。 所以我添加了一个isSquareValid(),但这样做填充了我的9x9网格0。 我不确定怎么做,所以我以一种丑陋的方式做到了(检查方格是否为45),如果它没有,那么它返回false。我不明白为什么它用0填充我的网格。

#include <vector>
#include <iostream>
#include <cstdlib>
#include <numeric>
#include <time.h>
#include <string>
#include <sstream>

using std::vector;
using std::pair;

// helper for std::accumulate
bool logical_and(bool x, bool y) {
    return x & y;
}

class Grid {
public:
    typedef int ElementType;
    typedef vector< vector<ElementType> > GridElements;

    Grid(const int linesize) :
        linesize_(linesize)
    {
        srand(time(NULL));

        // resizes to linesize_ rows & columns, with initial values == 0
        gridElements_.resize(linesize_, vector<ElementType>(linesize_, 0));
    }

    // use like this: cout << grid.to_s();
    std::string to_s() const {
        std::stringstream ss;
        for (int row = 0; row < gridElements_.size(); row++) {
            for (int col = 0; col < gridElements_[row].size(); col++) {
                ss << gridElements_[row][col] << " ";
            }
            ss << std::endl;
        }

        ss << std::endl;

        return ss.str();
    }

    // return true if there are no repeated numbers within filled elements in
    // rows/columns, false otherwise
    bool isValid() const {
        // you would also need to write and call a checkSquare method if you're doing a sudoku puzzle
        for (int i = 0; i < linesize_; i++) {
            if (!isRowValid(i) || !isColValid(i) || !isSquareValid()) {
                return false;
            }
        }

        return true;
    }

    // the recursive function that actually puts values in the grid elements
    // max recursion depth (I think) is linesize_^2
    bool fill(int row, int col) {
        // stopping conditions
        if (!isValid()) {
            return false;
        }
        if ((row == linesize_) || (col == linesize_)) {
            return true;
        }

        int nextCol = (col + 1) % linesize_;
        int nextRow = row;
        if (nextCol < col) {
            nextRow++;
        }

        // keep a record of what numbers have been tried in this element
        vector<bool> attemptedNumbers(linesize_ + 1, false);
        attemptedNumbers[0] = true;

        // We will continue choosing values for gridElements_[row][col]
        // as long as we haven't tried all the valid numbers, and as long as
        // the rest of the grid is not valid with this choice
        int value = 0;
        bool triedAllNumbers = false;
        bool restOfGridValid = false;
        while (!triedAllNumbers && !restOfGridValid) {
            while (attemptedNumbers[value]) {
                value = rand() % linesize_ + 1;
            }
            attemptedNumbers[value] = true;
            gridElements_[row][col] = value;

            // uncomment this for debugging/intermediate grids
            //std::cout << to_s();

            // triedAllNumbers == true if all the numbers in [1, linesize_] have been tried
            triedAllNumbers = std::accumulate(attemptedNumbers.begin(), attemptedNumbers.end(), true, logical_and);
            restOfGridValid = fill(nextRow, nextCol);
        }
        if (triedAllNumbers && !restOfGridValid) {
            // couldn't find a valid number for this location
            gridElements_[row][col] = 0;
        }

        return restOfGridValid;
    }

private:
    // checks that a number is used only once in the row
    // assumes that values in gridElements_ are in [1, linesize_]
    // return false when the row contains repeated values, true otherwise
    bool isRowValid(int row) const 
    {
        vector<bool> numPresent(linesize_ + 1, false);

        for (int i = 0; i < linesize_; i++) {
            int element = gridElements_[row][i];

            if (element != 0) {
                if (numPresent[element]) {
                    return false;
                }
                else {
                    numPresent[element] = true;
                }
            }
            // don't do anything if element == 0
        }

        return true;
    }

    // checks that a number is used only once in the column
    // assumes that values in gridElements_ are in [1, linesize_]
    // return false when the column contains repeated values, true otherwise
    bool isColValid(int col) const 
    {
        vector<bool> numPresent(linesize_ + 1, false);

        for (int i = 0; i < linesize_; i++) {
            int element = gridElements_[i][col];
            if (element != 0) {
                if (numPresent[element]) {
                    return false;
                }
                else {
                    numPresent[element] = true;
                }
            }
            else {

                break;
            }
        }

        return true;
    }

    bool isSquareValid() const
    {
        int square1 = gridElements_[0][0] + gridElements_[0][1] + gridElements_[0][2] + gridElements_[1][0] + gridElements_[2][0] + gridElements_[2][1] + gridElements_[1][1] + gridElements_[1][2] + gridElements_[2][2];
        int square2 = gridElements_[3][0] + gridElements_[4][0] + gridElements_[5][0] + gridElements_[3][1] + gridElements_[3][2] + gridElements_[4][1] + gridElements_[4][2] + gridElements_[5][1] + gridElements_[5][2];
        int square3 = gridElements_[6][0] + gridElements_[7][0] + gridElements_[8][0] + gridElements_[6][1] + gridElements_[6][2] + gridElements_[7][1] + gridElements_[7][2] + gridElements_[8][1] + gridElements_[8][2];
        int square4 = gridElements_[0][3] + gridElements_[0][4] + gridElements_[0][5] + gridElements_[1][3] + gridElements_[1][4] + gridElements_[1][5] + gridElements_[2][3] + gridElements_[2][4] + gridElements_[2][5];
        int square5 = gridElements_[0][6] + gridElements_[0][7] + gridElements_[0][8] + gridElements_[1][6] + gridElements_[1][7] + gridElements_[1][8] + gridElements_[2][6] + gridElements_[2][7] + gridElements_[2][8];
        int square6 = gridElements_[3][3] + gridElements_[3][4] + gridElements_[3][5] + gridElements_[4][3] + gridElements_[4][4] + gridElements_[4][5] + gridElements_[5][3] + gridElements_[5][4] + gridElements_[5][5];
        int square7 = gridElements_[6][3] + gridElements_[7][3] + gridElements_[8][3] + gridElements_[6][4] + gridElements_[7][4] + gridElements_[8][4] + gridElements_[6][5] + gridElements_[7][5] + gridElements_[8][5];
        int square8 = gridElements_[3][6] + gridElements_[3][7] + gridElements_[3][8] + gridElements_[4][6] + gridElements_[4][7] + gridElements_[4][8] + gridElements_[5][6] + gridElements_[5][7] + gridElements_[5][8];
        int square9 = gridElements_[6][6] + gridElements_[6][7] + gridElements_[6][8] + gridElements_[7][6] + gridElements_[7][7] + gridElements_[7][8] + gridElements_[8][6] + gridElements_[8][7] + gridElements_[8][8]; /*obsolète */
        if (square1 != 45)
            return false;
        if (square2 != 45)
            return false;
        if (square3 != 45)
            return false;
        if (square4 != 45)
            return false;
        if (square5 != 45)
            return false;
        if (square6 != 45)
            return false;
        if (square7 != 45)
            return false;
        if (square8 != 45)
            return false;
        if (square9 != 45)
            return false;
        return true;
    }

    // the size of each row/column
    int linesize_;

    // the 2d array
    GridElements gridElements_;
};




int main(int argc, char** argv) {
    // 9x9 grid
    Grid grid(9);

    // pretty sure this is mathematically guaranteed to always return true, assuming the algorithm is implemented correctly ;)
    grid.fill(0, 0);

    std::cout << grid.to_s();
    system("pause");
}

1 个答案:

答案 0 :(得分:1)

重写:所以我注意到我误解了你的填充功能,并且不得不做一些测试以使检查工作,因为我最初的想法是不可行的。我没有查看评论中引用的论文,这可能会给你一个更优化的解决方案,但试图扩展你的ansatz以包括正确的方块。最后,最好的方法是简单地调整attemptNumbers数组:它预先填充了所讨论的正方形中已存在的所有数字。这需要在课堂上使用几个辅助函数:activeSquareStartactiveSquareMembersnumsInSquare以及作为检查器,截至目前仍然失败,isSquareValid

请注意,我使用的确切代码具有结构化绑定,并且必须使用-std=c++17之类的标记或其他类似的标记进行编译:

g++ -g -std=c++17 sudoku.cpp

g++ -O3 -std=c++17 -DNDEBUG sudoku.cpp

表示没有调试输出。 Visual Studio必须存在类似的标志,我只是不知道它们是什么。除了在项目属性中的某处设置NDEBUG,这可能是VS的默认行为。

#include <vector>
#include <iostream>
#include <cstdlib>
#include <numeric>
#include <time.h>
#include <string>
#include <sstream>
#include <cmath>
#include <iterator>
#include <iomanip>

using std::vector;

// helper for std::accumulate
bool logical_and(bool x, bool y) {
    return x & y;
}
 //because I was curious how many cycles of filling attempts are required
static ulong cycles = 0;

class Grid {
    public:
        typedef int ElementType;
        typedef vector< vector<ElementType> > GridElements;
        typedef std::pair <size_t, size_t> Coordinate;

        Grid(const size_t linesize) :
            linesize_(linesize)
    {
        srand(time(NULL));

        // some check to make sure I can create subsquares
        // I think it needs square numbers so that subsquares work correctly
        // using any int != 0 is true
        if( sqrt(linesize_)*sqrt(linesize_) != linesize_) {
            throw "ERROR: can't create required squares";
        }
        // resizes to linesize_ rows & columns, with initial values == 0
        gridElements_.resize(linesize_, vector<ElementType>(linesize_, 0));
    }

        // use like this: cout << grid.to_s();
        std::string to_s() const {
            std::stringstream ss;
            for (size_t row = 0; row < gridElements_.size(); row++) {
                if(row%(size_t)sqrt(linesize_) == 0){
                    ss << "\n";
                }
                for (size_t col = 0; col < gridElements_[row].size(); col++) {
                    if(col%(size_t)sqrt(linesize_) == 0){
                        ss << "| ";
                    }
                    ss << std::setw(2) << gridElements_[row][col] << std::setw(0) << " ";
                }
                ss << std::endl;
            }

            ss << std::endl;

            return ss.str();
        }

        // return true if there are no repeated numbers within filled elements in
        // rows/columns, false otherwise
        bool isValid() const {
            // you would also need to write and call a checkSquare method if you're doing a sudoku puzzle
            for (size_t i = 0; i < linesize_; i++) {
                if (!isRowValid(i) || !isColValid(i)) {
                    return false;
                }
            }

            // I had a isSquareValid function being called for all squares here, but that destroyed the puzzle (only 0s)
            // since valid squares are now invariants for this problem, I'll leave it to you to actually implement the check :)
            // it does test the correct squares, so far

            /* for(size_t i = 0; i < linesize_; i+=(size_t)sqrt(linesize_)){ */
            /*  for(size_t j = 0; j < linesize_; j+=(size_t)sqrt(linesize_)){ */
            /*      if(!isSquareValid(i,j)){ */
            /*          return false; */
            /*      } */
            /*  } */
            /* } */

            return true;
        }

        // the recursive function that actually puts values in the grid elements
        // max recursion depth (I think) is linesize_^2
        bool fill(size_t row, size_t col) {
            // stopping conditions
            if (!isValid()) {
                return false;
            }
            if ((row == linesize_) || (col == linesize_)) {
                return true;
            }

            size_t nextCol = (col + 1) % linesize_;
            size_t nextRow = row;
            if (nextCol < col) {
                nextRow++;
            }

            // keep a record of what numbers have been tried in this element
            // exclude all numbers already present in the active squre before beginning
            vector<bool> attemptedNumbers = numsInSquare(row, col);
            attemptedNumbers[0] = true;

            // We will continue choosing values for gridElements_[row][col]
            // as long as we haven't tried all the valid numbers, and as long as
            // the rest of the grid is not valid with this choice
            int value = 0;
            bool triedAllNumbers = false;
            bool restOfGridValid = false;
            while (!triedAllNumbers && !restOfGridValid) {

                while (attemptedNumbers[value]) {
                    value = rand() % linesize_ + 1;
                }
                attemptedNumbers[value] = true;
                gridElements_[row][col] = value;

                // uncomment this for debugging/intermediate grids
#ifndef NDEBUG
                std::cout << ++cycles << '\n';
                std::cout << to_s();
#endif

                // triedAllNumbers == true if all the numbers in [1, linesize_] have been tried
                triedAllNumbers = std::accumulate(attemptedNumbers.begin(), attemptedNumbers.end(), true, logical_and);
                restOfGridValid = fill(nextRow, nextCol);
            } 
            if (triedAllNumbers && !restOfGridValid) {
                // couldn't find a valid number for this location
                gridElements_[row][col] = 0;
            }

            return restOfGridValid;
        }

    private:
        // checks that a number is used only once in the row
        // assumes that values in gridElements_ are in [1, linesize_]
        // return false when the row contains repeated values, true otherwise
        bool isRowValid(size_t row) const {
            vector<bool> numPresent (linesize_ + 1, false);

            for (size_t i = 0; i < linesize_; i++) {
                int element = gridElements_[row][i];

                if (element != 0) {
                    if (numPresent[element]) {
                        return false;
                    }
                    else {
                        numPresent[element] = true;
                    }
                }
                // don't do anything if element == 0
            }

            return true;
        }

        // checks that a number is used only once in the column
        // assumes that values in gridElements_ are in [1, linesize_]
        // return false when the column contains repeated values, true otherwise
        bool isColValid(int col) const {
            vector<bool> numPresent (linesize_ + 1, false);

            for (size_t i = 0; i < linesize_; i++) {
                int element = gridElements_[i][col];

                if (element != 0) {
                    if (numPresent[element]) {
                        return false;
                    }
                    else {
                        numPresent[element] = true;
                    }
                }
                else {

                    break;
                }
            }

            return true;
        }

//NEW HELPER FUNCTIONS START HERE

        //upper left corner of square the coordinate (row,col) lies in
        Coordinate activeSquareStart(size_t row, size_t col) const {
            return Coordinate{ row - (row % int(std::sqrt(linesize_))), col - (col % int(std::sqrt(linesize_)))};
        }

        vector<Coordinate> activeSquareMembers(size_t const row, size_t const col) const{
            //requires -std=c++17 or similar flag for structured binding auto [..] = .. to work!
            // if that is a problem, create a temporary std::pair instead
            auto [x, y] = activeSquareStart(row, col); 
            vector<Coordinate> members;
            members.reserve(linesize_);
            for(size_t i=0; i<sqrt(linesize_); ++i){
                for(size_t j=0; j<sqrt(linesize_); ++j){
                    members.push_back({x+i, y+j});
                }
            }
            return members;
        }

        vector<bool> numsInSquare(size_t const row, size_t const col) const{
            vector<bool> numPresent (linesize_ + 1, false);
            numPresent[0] = true;
            vector<Coordinate> squareMembers = activeSquareMembers(row, col);
            for(auto e : squareMembers){
                numPresent[ gridElements_.at(e.first).at(e.second) ] = true;
            }
            return numPresent;
        }

        bool isSquareValid(size_t const row, size_t const col) const{
            vector<bool> numPresent = numsInSquare(row, col);
            // logical error probably here
            return std::accumulate(numPresent.begin(), numPresent.end(), true, logical_and);
        }

        // the size of each row/column
        size_t linesize_;

        // the 2d array
        GridElements gridElements_;
};


int main(int argc, char** argv) {
    // 9x9 grid
    Grid grid(16);

    // pretty sure this is mathematically guaranteed to always return true, assuming the algorithm is implemented correctly ;)
    grid.fill(0, 0);

    std::cout << grid.to_s();
}