diff options
Diffstat (limited to 'src/main.zig')
-rw-r--r-- | src/main.zig | 218 |
1 files changed, 187 insertions, 31 deletions
diff --git a/src/main.zig b/src/main.zig index 3301b7e..72be820 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,46 +1,202 @@ -//! By convention, main.zig is where your main function lives in the case that -//! you are building an executable. If you are making a library, the convention -//! is to delete this file and start with root.zig instead. +const SudokuSolver = struct { + const countEql = std.simd.countElementsWithValue; + const join = std.simd.join; + // Each cell exists in memory three times. + // This makes it easy to compare a guess against all relevant cells simultaneously. + rows: [9]@Vector(9, u8), + cols: [9]@Vector(9, u8), + boxes: [9]@Vector(9, u8), + + fn init(board: [9][9]u8) !SudokuSolver { + var res: SudokuSolver = .{ + .rows = undefined, + .cols = undefined, + .boxes = undefined, + }; + for (0..9) |row| { + for (0..9) |col| { + const cell = board[row][col]; + if (cell > 9) return error.InvalidValue; + res.setCell(row, col, cell); + } + } + + return res; + } + + fn possibleMovesForCell(self: SudokuSolver, row: usize, col: usize) std.StaticBitSet(9) { + var cell_guesses: std.StaticBitSet(9) = .initEmpty(); + for (1..10) |n| { + // A number is a possible move iff it does not already exist in the current row, col, or box. + if (countEql( + join(self.rows[row], join(self.cols[col], self.boxes[boxOf(row, col)])), + @intCast(n), + ) == 0) { + cell_guesses.set(n - 1); + } + } + + return cell_guesses; + } + + fn solveEasyBoard(self: *SudokuSolver) !void { + while (!self.isSolved()) { + var moved_this_iteration = false; + for (self.rows, 0..) |row, row_i| { + for (@as([9]u8, row), 0..) |cell, col_i| { + if (cell == 0) { + const possible_moves = self.possibleMovesForCell(row_i, col_i); + if (possible_moves.count() == 1) { + self.setCell( + row_i, + col_i, + @intCast(possible_moves.findFirstSet().? + 1), + ); + moved_this_iteration = true; + } + } + } + } + if (!moved_this_iteration) return error.UnableToSolve; + } + } + + fn isSolved(self: SudokuSolver) bool { + for (self.rows) |row| { + if (countEql(row, 0) > 0) return false; + } + return true; + } + + fn setCell(self: *SudokuSolver, row: usize, col: usize, value: u8) void { + self.rows[row][col] = value; + self.cols[col][row] = value; + self.boxes[boxOf(row, col)][cellOfInBox(row, col)] = value; + } + + fn boxOf(row: usize, col: usize) usize { + // Due to integer division, this is not the same as just adding row. + return (row / 3) * 3 + col / 3; + } + + fn cellOfInBox(row: usize, col: usize) usize { + return (row % 3) * 3 + col % 3; + } + + fn display(self: SudokuSolver, writer: std.io.AnyWriter) !void { + for (self.rows, 0..) |row, i| { + // Can't loop over row directly? + for (@as([9]u8, row), 0..) |cell, j| { + try writer.print("{d} ", .{cell}); + if (j % 3 == 2 and j < 8) { + try writer.print("| ", .{}); + } + } + try writer.print("\n", .{}); + if (i % 3 == 2 and i < 8) { + try writer.print("{s}\n", .{"-" ** 21}); + } + } + } +}; pub fn main() !void { - // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`) - std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + var board: [9][9]u8 = undefined; - // stdout is for the actual output of your application, for example if you - // are implementing gzip, then only the compressed bytes should be sent to - // stdout, not any debugging messages. - const stdout_file = std.io.getStdOut().writer(); - var bw = std.io.bufferedWriter(stdout_file); - const stdout = bw.writer(); + const stdout = std.io.getStdOut().writer().any(); + const stdin = std.io.getStdIn().reader().any(); - try stdout.print("Run `zig build test` to run the tests.\n", .{}); + for (0..9) |row| { + for (0..9) |col| { + const byte = try stdin.readByte(); + board[row][col] = try std.fmt.charToDigit(byte, 10); + } + } + + // Unreachable because we validate while parsing. + var solver = SudokuSolver.init(board) catch unreachable; + + solver.solveEasyBoard() catch |err| { + std.debug.print("Unable to solve board\n", .{}); + return err; + }; - try bw.flush(); // Don't forget to flush! + try solver.display(stdout); } -test "simple test" { - var list = std.ArrayList(i32).init(std.testing.allocator); - defer list.deinit(); // Try commenting this out and see if zig detects the memory leak! - try list.append(42); - try std.testing.expectEqual(@as(i32, 42), list.pop()); +const std = @import("std"); + +test "boxOf" { + const expectEql = std.testing.expectEqual; + try expectEql(0, SudokuSolver.boxOf(0, 0)); + try expectEql(1, SudokuSolver.boxOf(0, 3)); + try expectEql(2, SudokuSolver.boxOf(2, 8)); + try expectEql(4, SudokuSolver.boxOf(4, 5)); } -test "use other module" { - try std.testing.expectEqual(@as(i32, 150), lib.add(100, 50)); +test "cellOfInBox" { + const expectEql = std.testing.expectEqual; + try expectEql(0, SudokuSolver.cellOfInBox(0, 0)); + try expectEql(1, SudokuSolver.cellOfInBox(0, 1)); + try expectEql(2, SudokuSolver.cellOfInBox(0, 2)); + try expectEql(3, SudokuSolver.cellOfInBox(1, 0)); + + try expectEql(0, SudokuSolver.cellOfInBox(3, 3)); + try expectEql(0, SudokuSolver.cellOfInBox(6, 6)); } -test "fuzz example" { - const Context = struct { - fn testOne(context: @This(), input: []const u8) anyerror!void { - _ = context; - // Try passing `--fuzz` to `zig build test` and see if it manages to fail this test case! - try std.testing.expect(!std.mem.eql(u8, "canyoufindme", input)); +test "get available moves for easy board" { + // Taken from https://ziggit.dev/t/i-wrote-a-simple-sudoku-solver/9924/14?u=zambyte + if (false) { + const board: [9][9]u8 = .{ + .{ 4, 0, 3, 5, 0, 0, 0, 9, 6 }, + .{ 5, 0, 7, 8, 0, 0, 3, 0, 0 }, + .{ 0, 8, 0, 0, 0, 0, 0, 5, 4 }, + .{ 0, 0, 0, 7, 0, 6, 0, 2, 3 }, + .{ 0, 0, 0, 0, 2, 0, 0, 0, 0 }, + .{ 9, 5, 0, 3, 0, 4, 0, 0, 0 }, + .{ 7, 9, 0, 0, 0, 0, 0, 6, 0 }, + .{ 0, 0, 4, 0, 0, 7, 5, 0, 8 }, + .{ 8, 3, 0, 0, 0, 5, 4, 0, 9 }, + }; + + const solver: SudokuSolver = SudokuSolver.init(.{ .board = board }) catch unreachable; + + for (board, 0..) |row, row_i| { + for (row, 0..) |cell, col_i| { + if (cell == 0) { + const possible_moves = solver.possibleMovesForCell(row_i, col_i); + if (possible_moves.count() == 1) { + std.debug.print("row: {}, col: {}, value: {}\n", .{ row_i, col_i, possible_moves.findFirstSet().? }); + } + } + } } - }; - try std.testing.fuzz(Context{}, Context.testOne, .{}); + } + return error.SkipZigTest; } -const std = @import("std"); +test "Solve easy board" { + // Taken from https://ziggit.dev/t/i-wrote-a-simple-sudoku-solver/9924/14?u=zambyte + const board: [9][9]u8 = .{ + .{ 4, 0, 3, 5, 0, 0, 0, 9, 6 }, + .{ 5, 0, 7, 8, 0, 0, 3, 0, 0 }, + .{ 0, 8, 0, 0, 0, 0, 0, 5, 4 }, + .{ 0, 0, 0, 7, 0, 6, 0, 2, 3 }, + .{ 0, 0, 0, 0, 2, 0, 0, 0, 0 }, + .{ 9, 5, 0, 3, 0, 4, 0, 0, 0 }, + .{ 7, 9, 0, 0, 0, 0, 0, 6, 0 }, + .{ 0, 0, 4, 0, 0, 7, 5, 0, 8 }, + .{ 8, 3, 0, 0, 0, 5, 4, 0, 9 }, + }; -/// This imports the separate module containing `root.zig`. Take a look in `build.zig` for details. -const lib = @import("sudoku_solver_zig_lib"); + var solver: SudokuSolver = SudokuSolver.init(board) catch unreachable; + + const stderr = std.io.getStdErr().writer().any(); + std.debug.print("input:\n", .{}); + try solver.display(stderr); + std.debug.print("output:\n", .{}); + + try solver.solveEasyBoard(); + try solver.display(stderr); +} |