In the previous post, I implemented a Sudoku solver. Unfortunately, it does not work yet. Let’s fix it!

Debugging the solver in the current state of the code is hard. The test fails because the expected solution is not equal to the actual solution returned, but since I have no way of visualizing the Grid it’s hard to say what the difference is.

Therefore, my approach here would be to write a function to print the grids. This function, with the current design, would naturally live as method of the Grid class. The Grid class seems to have become the place where I dump most of the code and has a number of responsibilities:

  • Hold & fetch grid data (which is the main goal of the class).
  • Find empty cells.
  • Compare equality with other grids.
  • Modify Grid.
  • And now I’d like to add: print Grid for debugging.

This does not sound right. Ideally, the class should have a single responsibility: hold and fetch data about grids. Equality could also be part of of this, because it’s an implementation of the concept of equality for the Grid object. Modification also sounds ok as it’s very data related. Other algorithms should not be there though.

The initial mistake was made by adding the search for empty cells to this class, and I shall not repeat it again now. What I actually need is a method to iterate over all cells. With that I can extract findEmptyCell.

Thinking about this new accessor method interface, I’d just return an array of integers. I’m pretty sure in Java there would be a better (lazy) way, but I’m not that into the language and do not have internet access right now. The interface sounds simple enough to me.

I’ll attempt a test for the first:

@Test
public void testIter() throws InvalidGridException {
    Grid grid = new Grid(KNOWN_GRID);
    int i = 0;
    for (int value: grid.iter()) {
        assertEquals(KNOWN_GRID[i], value);
        ++i;
    }
}

Good enough. The code I’m writing to implement this, looks like this:

public int[] iter() {
    return array.clone();
}

Passing! I’m cloning to avoid that the Grid instance is mutated. Now I can move findEmptyCell out. I’m not liking where this is headed: I will have a pointer to an internal array of a class, however supposedly opaque, which is handled outside the class.

I really need to decide on an interface to identify a cell in the Grid. To me, it seems that the index is bad design, and I should really use the coordinates instead. That way, the internal data representation is properly encapsulated and the abstraction is not leaky.

At this point, it might be good to actually refactor before proceeding, instead of trying to fix the solver. If I continue on this road, I’ll end up having to rewrite everything later. I should at least refactor some things right now.

The CellRef Class

Let’s try to design this a bit better. I’ll base my refactoring on the new CellRef class:

public class CellRef {
    public final int x, y;

    CellRef(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

This is what I call a “Data class”, that is a container for data that has no methods attached. You can think of it as a struct as it appears in C.

The first obvious place where I can use this, is converting getCellValue(int, int) to getCellValue(CellRef):

@Test
void testGetCellValue() throws InvalidGridException, OutOfGridException {
    Grid grid = new Grid(KNOWN_GRID);
    assertEquals(9, grid.getCellValue(new CellRef(0, 1)));
    assertEquals(3, grid.getCellValue(new CellRef(7, 4)));
    assertEquals(0, grid.getCellValue(new CellRef(8, 8)));
    assertEquals(8, grid.getCellValue(new CellRef(8, 0)));
    assertEquals(2, grid.getCellValue(new CellRef(0, 8)));
}

(I also changed all other tests and code using this function in a very similar way.)

Then change the method to this:

public int getCellValue(CellRef ref) throws OutOfGridException {
    int x = ref.x;
    int y = ref.y;

    if (x < 0 || x >= GRID_SIZE || y < 0 || y >= GRID_SIZE) {
        throw new OutOfGridException(String.format(
                "Coordinate out of grid bounds: x=%d,y=%d",
                x, y
        ));
    }

    return array[y*GRID_SIZE + x];
}

The tests are still passing. I won the battle but not the war yet.

The Feared Refactoring

Now, in the Solver I need to iterate over rows, columns and blocks. That’s ugly because Solver needs to know what the magic number 9 for the Sudoku size. The Solver just wants to check for duplicates. It’s not concerned about the data layout.

I could write a new bunch of methods to fetch rows and columns. Some tests:

void assertArrayEquals(int[] actual, int[] expected) throws AssertionError {
    assertEquals(actual.length, expected.length);
    for (int i = 0; i < actual.length; ++i) {
        assertEquals(actual[i], expected[i]);
    }
}

@Test
void testGetRows() throws InvalidGridException {
    Grid grid = new Grid(KNOWN_GRID);
    int[][] rows = grid.getRows();
    assertArrayEquals(new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8}, rows[0]);
    assertArrayEquals(new int[]{9, 0, 1, 2, 3, 4, 5, 6, 7}, rows[1]);
    assertArrayEquals(new int[]{8, 9, 0, 1, 2, 3, 4, 5, 6}, rows[2]);
    assertArrayEquals(new int[]{7, 8, 9, 0, 1, 2, 3, 4, 5}, rows[3]);
    assertArrayEquals(new int[]{6, 7, 8, 9, 0, 1, 2, 3, 4}, rows[4]);
    assertArrayEquals(new int[]{5, 6, 7, 8, 9, 0, 1, 2, 3}, rows[5]);
    assertArrayEquals(new int[]{4, 5, 6, 7, 8, 9, 0, 1, 2}, rows[6]);
    assertArrayEquals(new int[]{3, 4, 5, 6, 7, 8, 9, 0, 1}, rows[7]);
    assertArrayEquals(new int[]{2, 3, 4, 5, 6, 7, 8, 9, 0}, rows[8]);
}

@Test
void testGetColumns() throws InvalidGridException {
    Grid grid = new Grid(KNOWN_GRID);
    int[][] columns = grid.getColumns();
    assertArrayEquals(new int[]{0, 9, 8, 7, 6, 5, 4, 3, 2}, columns[0]);
    assertArrayEquals(new int[]{1, 0, 9, 8, 7, 6, 5, 4, 3}, columns[1]);
    assertArrayEquals(new int[]{2, 1, 0, 9, 8, 7, 6, 5, 4}, columns[2]);
    assertArrayEquals(new int[]{3, 2, 1, 0, 9, 8, 7, 6, 5}, columns[3]);
    assertArrayEquals(new int[]{4, 3, 2, 1, 0, 9, 8, 7, 6}, columns[4]);
    assertArrayEquals(new int[]{5, 4, 3, 2, 1, 0, 9, 8, 7}, columns[5]);
    assertArrayEquals(new int[]{6, 5, 4, 3, 2, 1, 0, 9, 8}, columns[6]);
    assertArrayEquals(new int[]{7, 6, 5, 4, 3, 2, 1, 0, 9}, columns[7]);
    assertArrayEquals(new int[]{8, 7, 6, 5, 4, 3, 2, 1, 0}, columns[8]);
}

@Test
void testGetBoxes() throws InvalidGridException {
    Grid grid = new Grid(KNOWN_GRID);
    int[][] boxes = grid.getBoxes();
    assertArrayEquals(new int[]{0, 1, 2, 9, 0, 1, 8, 9, 0}, boxes[0]);
    assertArrayEquals(new int[]{3, 4, 5, 2, 3, 4, 1, 2, 3}, boxes[1]);
    assertArrayEquals(new int[]{6, 7, 8, 5, 6, 7, 4, 5, 6}, boxes[2]);
    assertArrayEquals(new int[]{7, 8, 9, 6, 7, 8, 5, 6, 7}, boxes[3]);
    assertArrayEquals(new int[]{0, 1, 2, 9, 0, 1, 8, 9, 0}, boxes[4]);
    assertArrayEquals(new int[]{3, 4, 5, 2, 3, 4, 1, 2, 3}, boxes[5]);
    assertArrayEquals(new int[]{4, 5, 6, 3, 4, 5, 2, 3, 4}, boxes[6]);
    assertArrayEquals(new int[]{7, 8, 9, 6, 7, 8, 5, 6, 7}, boxes[7]);
    assertArrayEquals(new int[]{0, 1, 2, 9, 0, 1, 8, 9, 0}, boxes[8]);
}

I hard-coded the values. It’s a miracle if I got this right. Luckily, the tests won’t pass if I made a mistake. My tentative implementation of getRows is:

public int[][] getRows() {
    List<int[]> rows = new ArrayList<>();
    for (int i = 0; i < GRID_SIZE; ++i) {
        rows.add(new int[]{
                i, i+1, i+2, i+3, i+4, i+5, i+6, i+7, i+8,
        });
    }
    return rows.toArray(new int[][]{});
}

Now, the other methods would be the same, just using a different ways of indexing. I’ll move it all to a private method to avoid duplication:

int[][] collect(int stride, int[] offsets) {
    int[][] groups = new int[GRID_SIZE][GRID_SIZE];
    for (int i = 0; i < GRID_SIZE; ++i) {
        for (int j = 0; j < GRID_SIZE; ++j) {
            int index = i*stride + offsets[j];
            groups[i][j] = array[index];
        }
    }
    return groups;
}

public int[][] getRows() {
    return collect(9, new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8});
}

public int[][] getColumns() {
    return collect(1, new int[]{0, 9, 18, 27, 36, 45, 54, 63, 72});
}

public int[][] getBoxes() {
    return collect(3, new int[]{0, 1, 2, 9, 10, 11, 18, 19, 20});
}

I also ditched the List to avoid some allocations. I’m not concerned about memory usage as we’re talking of a tiny bunch of integers.

The tests for getBoxes are still failing. The reason is that with a simple stride, I cannot represent the indexing of boxes. I need some more sophisticated collect() method. I find it clean and simple to pass a function to compute the next stride.

int[][] collect(Function<Integer, Integer> getStride, int[] offsets) {
    int[][] groups = new int[GRID_SIZE][GRID_SIZE];
    for (int i = 0; i < GRID_SIZE; ++i) {
        for (int j = 0; j < GRID_SIZE; ++j) {
            int index = getStride.apply(i) + offsets[j];
            groups[i][j] = array[index];
        }
    }
    return groups;
}

public int[][] getBoxes() {
    return collect(
        (i) -> (i / 3) * 27 + (i % 3) * 3,
        new int[]{0, 1, 2, 9, 10, 11, 18, 19, 20}
    );
}

This now passes (I also performed the required changes to the other methods).

With this in place, I can now refactor Solver. This is the state of the code right now:

static void validateRows(Grid state) throws
        OutOfGridException,
        InvalidInitialStateException
{
    for (int row = 0; row < 9; ++row) {
        Set<Integer> visited = new HashSet<>();
        for (int i = 0; i < 9; ++i) {
             int value = state.getCellValue(new CellRef(i, row));
             if (value == 0) continue;
             if (visited.contains(value)) {
                 throw new InvalidInitialStateException(String.format(
                         "Row %d contains duplicate value %d",
                         row, value
                 ));
             }
             visited.add(value);
        }
    }
}

This can be easily refactored into:

static void validateRows(Grid state) throws InvalidInitialStateException {
    for (int[] row: state.getRows()) {
        Set<Integer> visited = new HashSet<>();
        for (int value: row) {
             if (value == 0) continue;
             if (visited.contains(value)) {
                 throw new InvalidInitialStateException(String.format(
                         "Row %d contains duplicate value %d",
                         row, value
                 ));
             }
             visited.add(value);
        }
    }
}

But we can do even better, we can move all the logic to a private helper function to avoid duplication, just as we did in Grid.

static void validateUnique(int[][] groups, String setName)
    throws InvalidInitialStateException
{
    for (int i = 0; i < groups.length; ++i) {
        int[] group = groups[i];
        Set<Integer> visited = new HashSet<>();
        for (int value: group) {
            if (value == 0) continue;
            if (visited.contains(value)) {
                throw new InvalidInitialStateException(String.format(
                        "%s %d contains duplicate value %d",
                        setName, i, value
                ));
            }
            visited.add(value);
        }
    }
}

static void validateRows(Grid state) throws InvalidInitialStateException {
    validateUnique(state.getRows(), "Row");
}

static void validateColumns(Grid state) throws InvalidInitialStateException {
    validateUnique(state.getColumns(), "Column");
}

static void validateBoxes(Grid state) throws InvalidInitialStateException {
    validateUnique(state.getBoxes(), "Box");
}

Tests are still shining in green. How cool is it to refactor and being certain the stuff is still working? I remember those gloomy days where I would cross my fingers and hope for the best. Luckily, the testing sun shines now.

With the modifications above, I’m changing the error message a little for the Box case, but overall the code is much shorter and cleaner. I also got rid of that pesky 9, so the coupling is gone!

Finding Empty Cells

I continue to surf the Refactoring wave until it lasts.

I need to extract findEmptyCell from the Grid class, as I determined that I don’t want to have this method in the class. For this, I could convert the iter method at the start of this post. Instead, I’ll go for a simpler, perhaps uglier design, and drop iter from the code. I will just use getRows() to find the empty cell.

But first, I’ll move the tests to the SolverTest class. I’ll move findEmptyCell to Solver as for now I just need it there. I know it could be moved to an extra class for reuse, but for YAGNI reasons and laziness I’ll limit myself to the move.

// Tests moved to SolverTests from GridTests with some
// adaptations (commented below)
@ParameterizedTest
@ValueSource(ints = {0, 1, 77})
void testFindEmptyCell(int index) throws InvalidGridException {
    int[] data = IntStream.generate(() -> 1).limit(81).toArray();
    data[index] = 0;

    Grid grid = new Grid(data);
    // Use RefCell instead of index
    CellRef ref = Solver.findEmptyCell(grid);
    assertEquals(index % 9, ref.x);
    assertEquals(index / 9, ref.y);
}

@Test
void testFindEmptyCellNone() throws InvalidGridException {
    int[] data = IntStream.generate(() -> 1).limit(81).toArray();
    Grid grid = new Grid(data);
    // Expect null instead of -1
    assertEquals(null, Solver.findEmptyCell(grid));
}

That was easy. findEmptyCell will need to be a public method, which is a bit ugly as I’m polluting the interface. But if that enables better testing and easier refactoring later (the method has independent tests that can be moved effortlessly, as I just did), I’m fine with it.

I think that as programmers, we must decide to be pragmatic at times if we want to accomplish something. The point is also to keep improving the code every time instead of trying get it done perfectly.

The return value if there’s no cell is now null. I’m using 9 here again. I’ll have to fix it in the next step, but at least it’s in a test and not in the implementation itself.

Then, I move the method:

public static CellRef findEmptyCell(Grid state) {
    int[][] rows = state.getRows();
    for (int y = 0; y < rows.length; ++y) {
        int[] row = rows[y];
        for (int x = 0; x < row.length; ++x) {
            if (row[x] == 0) {
                return new CellRef(x, y);
            }
        }
    }
    return null;
}

It became static and a little longer, but I’m saving some time by reusing the existing getRows. The tests are passing.

Replacing Values

The last function that still needs to be changed is withCell. As customary, start by fixing the tests to accept CellRef instead of an index:

@Test
void testWithCell() throws InvalidGridException, OutOfGridException {
    CellRef ref = new CellRef(6, 1);

    Grid grid = new Grid(KNOWN_GRID);
    assertEquals(5, grid.getCellValue(ref));

    Grid newGrid = grid.withCell(ref, 7);
    assertEquals(5, grid.getCellValue(ref));
    assertEquals(7, newGrid.getCellValue(ref));
}

@ParameterizedTest
@ValueSource(ints = {-10, -1, 10, 100})
void testWithCellOutOfBounds(int value) throws InvalidGridException {
    Grid grid = new Grid(KNOWN_GRID);
    assertThrows(
            OutOfGridException.class,
            () -> grid.withCell(new CellRef(value, 4), 7)
    );
}

@ParameterizedTest
@ValueSource(ints = {-10, -1, 9, 100})
void testWithCellWrongValue(int value) throws InvalidGridException {
    Grid grid = new Grid(KNOWN_GRID);
    assertThrows(
            InvalidGridException.class,
            () -> grid.withCell(new CellRef(3, 7), value)
    );
}

In the first test, I created a variable for the reference. The others are just simple adaptations of the old tests to use the new interface. I also changed the bounds of the parameters to make sense for the new situation. Now that tests are red as blood, let’s fix the wounds:

public Grid withCell(CellRef ref, int value) throws
    OutOfGridException,
    InvalidGridException
{
    int index = ref.y*GRID_SIZE + ref.x;
    if (index < 0 || index >= array.length) {
        throw new OutOfGridException(
                String.format("Invalid index %d", index)
        );
    }
    if (value < 0 || value > GRID_SIZE) {
        throw new InvalidGridException(
                String.format("Cannot set value %d to cell", value)
        );
    }
    int[] data = array.clone();
    data[index] = value;
    return new Grid(data);
}

I actually just changed the signature and added the first line to compute the index. The out-of-bounds test fails because the code does not work. I can set CellRef(-4, 2) and currently that’s equivalent to CellRef(5, 1). I need to change the code:

public Grid withCell(CellRef ref, int value) throws
    OutOfGridException,
    InvalidGridException
{
    if (ref.x < 0 || ref.x >= GRID_SIZE || ref.y < 0 || ref.y >= GRID_SIZE) {
        throw new OutOfGridException(
                String.format("Invalid grid coordinates (%d, %d)", ref.x, ref.y)
        );
    }
    if (value < 0 || value > GRID_SIZE) {
        throw new InvalidGridException(
                String.format("Cannot set value %d to cell", value)
        );
    }
    int[] data = array.clone();
    int index = ref.y*GRID_SIZE + ref.x;
    data[index] = value;
    return new Grid(data);
}

That’s better, it now works. Another bug caught by testing!

As promise, I also fix the test above by removing that magic 9:

@Test
void testFindEmptyCell() throws
        InvalidGridException,
        OutOfGridException
{
    CellRef expRef = new CellRef(6, 1);

    int[] data = IntStream.generate(() -> 1).limit(81).toArray();
    Grid grid = new Grid(data).withCell(expRef, 0);

    CellRef ref = Solver.findEmptyCell(grid);
    assertEquals(expRef.x, ref.x);
    assertEquals(expRef.y, ref.y);
}

A last step to make everything compile is changing some data types in the solver algorithms to adapt to the new situation.

Printing the Grid!

Going back to the beginning, I wanted to visualize what is happening in the solver. I’ll add a private method to print a Grid content to Solver. I won’t write a test for this as it’s private and only a temporary aid during debugging:

static void printState(Grid state) {
    int[][] rows = state.getRows();
    for (int y = 0; y < rows.length; ++y) {
        if (y != 0 && (y % 3) == 0) System.out.printf("\n");

        int[] row = rows[y];
        for (int x = 0; x < row.length; ++x) {
            if (x != 0 && (x % 3) == 0) System.out.printf(" ");
            System.out.printf("%d ", row[x]);
        }
        System.out.printf("\n");
    }

    System.out.println("---");
}

This, manually tested for some state, prints:

2 0 0  0 0 0  4 0 8
0 0 0  0 3 4  0 2 5
0 7 0  8 0 0  0 0 0

7 0 9  0 0 0  0 3 0
1 0 8  2 4 0  5 7 6
6 0 2  3 1 7  9 0 4

3 6 0  4 9 0  0 0 0
0 2 0  0 7 8  6 0 0
4 0 0  0 0 0  0 5 0
---

Final Thoughts

In this post, I completed a refactoring of the code-base to clean up the classes, move functionality to suitable places and improve the encapsulation.

Am I happy with the result? Not really, but it’s better than it was before for sure. Methods are in better places, and there is now a consistent interface to the Grid class. A lot of places do not break abstraction anymore. There’s still improvements that could be made I guess, but I will move on to debugging the algorithm in the next post.

As usual, you can get the code at Gitlab.