4
\$\begingroup\$

I know it may be fairly bad. I want to find out exactly how horrendous it is and any suggestions on how to improve are appreciated.

public class SudokuSolver_4{
    //Creating the main sudoku board
    int sudokuGraph[][] = new int[9][9];
    public boolean attemptAssignValue(int leftIndex, int upIndex){
        //Attempt to assign a value to the specified cell
        for(int x = 0; x < 10 ; x++){
            if(notContainedInRow(leftIndex, x) && notContainedInColumn(upIndex, x) && notContainedInSubGrid(leftIndex, upIndex, x)){
                sudokuGraph[leftIndex][upIndex] = x;    
                return true;
            }                   
        } 
        return false;
    }

    public boolean attemptAssignValue(int leftIndex, int upIndex, int startValue){
        //When the program backtracks and has to reassign a value, this overloaded version of the assignent function takes a third parameter - the value that the cell currently has, so that the program does not reassign it
        if(startValue != 9){
            for(int x = startValue; x < 10; x++){
                if(notContainedInRow(leftIndex, x) && notContainedInColumn(upIndex, x) && notContainedInSubGrid(leftIndex, upIndex, x)){
                    sudokuGraph[leftIndex][upIndex] = x;
                    return true;
                }
            }
            return false;
        }
        else{
            //This return is because it complained of no definite return statement in the method, as the other one was still in the larger if statement
            return false;
        }
    }

    public boolean notContainedInRow(int leftIndex, int numberToCheck){
        //Uses a simple nested loop to check if the particular number is anywhere in the row
        for (int upIndex = 0; upIndex < 9; upIndex++){
            if (sudokuGraph[leftIndex][upIndex] == numberToCheck){
                return false;
            }
        }
        return true;
    }

    public boolean notContainedInColumn(int upIndex, int numberToCheck){
        //Uses another nested loop to make sure that number is not anywhere in the column
        for (int leftIndex = 0; leftIndex < 9; leftIndex++){
            if(sudokuGraph[leftIndex][upIndex] == numberToCheck){
                return false;
            }
        }
        return true;
    }

    public boolean notContainedInSubGrid(int leftIndex, int upIndex, int numberToCheck){
        /* A peice of code that could definitely be improved. It is a successive if else ladder that check which grid a particular value is in. The logic is simple:
        It assumes that each grid is bound by two indexes - one on the upper side and one on the left side. So if the cell was in the first grid, it's leftIndex would be less than three
        and its upIndex would also be less than three. And so on. And as the ladder is if else, we don't need to define a greater than condition.*/
        int boundLeftIndex = 0;
        int boundUpIndex = 0;
        if (leftIndex < 3 && upIndex < 3){
            boundLeftIndex = 0;
            boundUpIndex = 0;
        }
        else if (leftIndex < 3 && upIndex < 6){
            boundLeftIndex = 0;
            boundUpIndex = 3;
        }
        else if (leftIndex < 3 && upIndex < 9){
            boundLeftIndex = 0;
            boundUpIndex = 6;
        }
        else if (leftIndex < 6 && upIndex < 3){
            boundLeftIndex = 3;
            boundUpIndex = 0;
        }
        else if (leftIndex < 6 && upIndex < 6){
            boundLeftIndex = 3;
            boundUpIndex = 3;
        }
        else if (leftIndex < 6 && upIndex < 9){
            boundLeftIndex = 3;
            boundUpIndex = 6;
        }
        else if (leftIndex < 9 && upIndex < 3){
            boundLeftIndex = 6;
            boundUpIndex = 0;
        }
        else if (leftIndex < 9 && upIndex < 6){
            boundLeftIndex = 6;
            boundUpIndex = 3;
        }
        else if (leftIndex < 9 && upIndex < 9){
            boundLeftIndex = 6;
            boundUpIndex = 6;
        }
        //Returns the boolean result of a call to another method which actually check whether or not the value is in the grid
        return notContainedInGridCheck(boundLeftIndex, boundUpIndex, numberToCheck);
    }

    public boolean notContainedInGridCheck(int leftIndex, int upIndex, int numberToCheck){
        //Uses a nested loop to traverse the subgrid and determine whether the value is present
        int x = leftIndex;
        int y = upIndex;
        for (x = leftIndex; x < leftIndex + 3; x++){
            for (y = upIndex; y < upIndex + 3; y++){
                if (sudokuGraph[x][y] == numberToCheck){
                    return false;
                }
            }
        }
        return true;
    }

    public int[] goBackOneCell(int leftIndex, int upIndex){
        /* Simply returns the index values for the previous cell on the graph. If it works properly there should be no need to specify a exception for the beginning 
        and end of the grid as the program shold never encounter that situation */
        if (upIndex == 0){
            upIndex = 8;
            leftIndex--;
        }
        else{
            upIndex--;
        }
        int newValues[] = new int[2];
        newValues[0] = leftIndex;
        newValues[1] = upIndex;        
        return newValues;
    }

    public int[] goAheadOneCell(int leftIndex, int upIndex){
        //Similar to the goBack function, this function returns the value of the next cell instead
        if (upIndex == 8){
            upIndex = 0;
            leftIndex++;
        }
        else{
            upIndex++;
        }
        int newValues[] = new int[2];
        newValues[0] = leftIndex;
        newValues[1] = upIndex;
        return newValues;
    }

    public void showTable(){
        //Used to print the graph 
        for (int leftIndex = 0; leftIndex < 9; leftIndex++){
            for (int upIndex = 0; upIndex < 9; upIndex++){
                System.out.print(sudokuGraph[leftIndex][upIndex]);
                System.out.print(" ");              
            }
            System.out.println();
            System.out.println("------------------------------------");
        }
        System.out.println();
        System.out.println("**END**");
        System.out.println();
    }

    public void control(){
        //The method which incorporates the above methods into a working solution
        int leftIndex = 0;
        int upIndex = 0;
        boolean assignmentAttempt;
        int values[] = new int[2];
        //The first assignment must be done outside of the loop as the boolean assigmentAttempt must be true for the loop to run
        //Yes, there are a hundred other ways of doing this (probably)
        assignmentAttempt = attemptAssignValue(leftIndex, upIndex);
        //Infinite loop, that is acually a non-infinite loop with a specific exit criteria (like most forever loops I presume)
        for(; ;){
            //The if statement that handles what to do after a successful assignment
            if(assignmentAttempt == true){
                //Checks if the end of the graph has been reached, if so, it breaks
                if(leftIndex == 8 && upIndex == 8){
                    showTable();
                    break;
                }
                //If the end has not been reached, it goes ahead one cell and attempts the assignment again. The outcome of the assignment is expressed by the assignmentAttempt variable
                values = goAheadOneCell(leftIndex, upIndex);
                leftIndex = values[0];
                upIndex = values[1];
                //The next to print statements were only for debugging, they serve no useful purpose to the end user
                System.out.print(leftIndex);
                System.out.print(upIndex);
                System.out.println();
                assignmentAttempt = attemptAssignValue(leftIndex, upIndex);
            }
            //The loop continues. If the assignment succeeded it goes back to the beginning after printing the table
            //Otherwise, it goes back one cell and attempts to assign again. Note that here, the overloaded version of the assignment method is used so that already previously assigned values are not reassigned
            //This if loop will continue to execute until another successful assignment occurs, ending the backtrack
            if(assignmentAttempt == false){
                sudokuGraph[leftIndex][upIndex] = 0;
                values = goBackOneCell(leftIndex, upIndex);
                leftIndex = values[0];
                upIndex = values[1];
                //Again, the next two print statements are only for debugging
                System.out.print(leftIndex);
                System.out.print(upIndex);
                System.out.println();
                //The same assignment variable is used for control
                assignmentAttempt = attemptAssignValue(leftIndex, upIndex, sudokuGraph[leftIndex][upIndex]);
            }
            //Printing the table at the end of each attempt
            showTable();     
        }  
    }
}
\$\endgroup\$
2
  • 3
    \$\begingroup\$ I'd suggest fixing the formatting of the code in the question and also adding either a driver program to show how to use this or enough explanation in the form of either narrative prose introducing the question or code comments. \$\endgroup\$
    – Edward
    Commented Feb 22, 2017 at 15:57
  • \$\begingroup\$ I fixed the code indent, but adding a driver would be awesome. \$\endgroup\$
    – rolfl
    Commented Feb 22, 2017 at 16:01

2 Answers 2

2
\$\begingroup\$

Lifecycle

What is the intended lifecycle of an object of this class? I don't see any constructor, and the field

    //Creating the main sudoku board
    int sudokuGraph[][] = new int[9][9];

is package-private. Is there some helper class which constructs an instance, fills in that array, and then calls control()? In my opinion it would be better to have a public constructor which takes the starting grid, and probably to only expose one other public method (something like public int[][] solve().


Representation

    //Creating the main sudoku board
    int sudokuGraph[][] = new int[9][9];

What I really want from that comment is an explanation of what the integers mean. Are they raw values from 1 to 9 with a sentinel indicating "not yet deduced"? Are they bitsets for possible values, ranging from 1 to 511?


Naming

    public boolean attemptAssignValue(int leftIndex, int upIndex){
        //Attempt to assign a value to the specified cell
        for(int x = 0; x < 10 ; x++){
            if(notContainedInRow(leftIndex, x) && notContainedInColumn(upIndex, x) && notContainedInSubGrid(leftIndex, upIndex, x)){

I can work out what leftIndex and upIndex mean, but I think row and column would be clearer, especially since the method names do call them row and column. Sort-of. Actually, I would have guessed that leftIndex meant column and upIndex meant row, not the other way round.

    public boolean notContainedInRow(int leftIndex, int numberToCheck){

and similar: it's generally clearer to have method names without not, because if you need to test whether the number is in the row you end up with !notContainedInRow, and the double-negative requires more thought. I would change it to containedInRow and invert the return values.


Subgrids

    public boolean notContainedInSubGrid(int leftIndex, int upIndex, int numberToCheck){
        /* A peice of code that could definitely be improved. It is a successive if else ladder that check which grid a particular value is in. The logic is simple:
        It assumes that each grid is bound by two indexes - one on the upper side and one on the left side. So if the cell was in the first grid, it's leftIndex would be less than three
        and its upIndex would also be less than three. And so on. And as the ladder is if else, we don't need to define a greater than condition.*/
        int boundLeftIndex = 0;
        int boundUpIndex = 0;
        if (leftIndex < 3 && upIndex < 3){
            boundLeftIndex = 0;
            boundUpIndex = 0;
        }
        else if (leftIndex < 3 && upIndex < 6){
            boundLeftIndex = 0;
            boundUpIndex = 3;
        }
        ...

In order, here are three better ways to do this:

  1. Split left and up. boundLeftIndex depends solely on leftIndex, so you could have two sets of three ifs each rather than one set of nine.

  2. Use switch.

    switch (leftIndex) {
        case 0: case 1: case 2: boundLeftIndex = 0; break;
        case 3: case 4: case 5: boundLeftIndex = 1; break;
        case 6: case 7: case 8: boundLeftIndex = 2; break;
        default: throw new RuntimeException("Shouldn't be reachable");
    }
    
  3. Use %. This gives the remainder after division.

    boundLeftIndex = leftIndex - (leftIndex % 3);
    boundUpIndex = upIndex - (upIndex % 3);
    

Don't compare Booleans

        if(assignmentAttempt == true){

could be just

        if(assignmentAttempt){

and that's more widely accepted style except in languages like C which don't have a proper Boolean type. Similarly

        if(assignmentAttempt == false){

should be

        if(!assignmentAttempt){

Tidy your code

            //The next to print statements were only for debugging, they serve no useful purpose to the end user
            System.out.print(leftIndex);
            System.out.print(upIndex);
            System.out.println();

So remove them before posting it for review.


KISS

    for(; ;){
        //The if statement that handles what to do after a successful assignment
        if(assignmentAttempt == true){
            ...
            assignmentAttempt = attemptAssignValue(leftIndex, upIndex);
        }
        //The loop continues. If the assignment succeeded it goes back to the beginning after printing the table
        //Otherwise, it goes back one cell and attempts to assign again. Note that here, the overloaded version of the assignment method is used so that already previously assigned values are not reassigned
        //This if loop will continue to execute until another successful assignment occurs, ending the backtrack
        if(assignmentAttempt == false){
            ...
            assignmentAttempt = attemptAssignValue(leftIndex, upIndex, sudokuGraph[leftIndex][upIndex]);
        }
        //Printing the table at the end of each attempt
        showTable();     
    }  

This is quite complex to reason about. It would be much simpler to reason about

    for(; ;){
        //The if statement that handles what to do after a successful assignment
        if(assignmentAttempt == true){
            ...
            assignmentAttempt = attemptAssignValue(leftIndex, upIndex);
        }
        //The loop continues. If the assignment succeeded it goes back to the beginning after printing the table
        //Otherwise, it goes back one cell and attempts to assign again. Note that here, the overloaded version of the assignment method is used so that already previously assigned values are not reassigned
        //This if loop will continue to execute until another successful assignment occurs, ending the backtrack
        else{
            ...
            assignmentAttempt = attemptAssignValue(leftIndex, upIndex, sudokuGraph[leftIndex][upIndex]);
        }
        //Printing the table at the end of each attempt
        showTable();     
    }  

Algorithm

Backtracking is usually the last resort of a Sudoku solver. You would be able to get a significant speed-up with some very simple rules: the only cell in a row/column/subgrid which isn't blocked from having a number can be filled with that number; a cell which is blocked from having eight numbers can be filled with the remaining one. That solves most "easy" Sudokus without any backtracking.

When backtracking is necessary, it's worth looking at heuristic approaches to pick the cell to guess rather than just taking the first one.

\$\endgroup\$
1
  • \$\begingroup\$ Also - thanks for all the tips on cleaner code (especially on the booleans). \$\endgroup\$
    – aneekul
    Commented Feb 24, 2017 at 12:19
1
\$\begingroup\$

I was surprised then I saw this, because I was about to finish my own Sudoku solver. And was planing to post it here.

My solution for subGrid indexes:

int xStart  = leftIndex / 3 *3
int yStart = upIndex / 3 *3;

and add check in notContainedInGridCheck for to not check index row and column( because you check it in other methods)

if (x == leftIndex) continue;

here is full subgrid check:

public boolean notContainedInSubGrid(int leftIndex, int upIndex, int numberToCheck){
    int xStart  = leftIndex / 3 *3
    int yStart = upIndex / 3 *3;
    for (int x = xStart; x < xStart + 3; x++){
        if (x == leftIndex) continue;
        for (int y = yStart; y < yStart + 3; y++){
            if (y == upIndex) continue;
            if (sudokuGraph[x][y] == numberToCheck){
                return false;
            }
        }
    }
    return true;
}
\$\endgroup\$
1
  • \$\begingroup\$ Many people told me about that. Now that I think of it - the way I did it is fairly absurd. Thanks! \$\endgroup\$
    – aneekul
    Commented Feb 24, 2017 at 12:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.