diff --git a/project_euler/problem_096/__init__.py b/project_euler/problem_096/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/project_euler/problem_096/sol1.py b/project_euler/problem_096/sol1.py new file mode 100644 index 000000000000..a48b50973820 --- /dev/null +++ b/project_euler/problem_096/sol1.py @@ -0,0 +1,209 @@ +""" +Project Euler Problem 96: https://projecteuler.net/problem=96 + +Problem Statement: +Su Doku (Japanese meaning number place) is the name given to a popular puzzle +concept. Its origin is unclear, but credit must be attributed to Leonhard +Euler who invented a similar, and much more difficult, puzzle idea called +Latin Squares. The objective of Su Doku puzzles, however, is to replace +the blanks (or zeros) in a 9 by 9 grid in such that each row, column, and +3 by 3 box contains each of the digits 1 to 9. Below is an example of a +typical starting puzzle grid and its solution grid. + +003020600 +900305001 +001806400 +008102900 +700000008 +006708200 +002609500 +800203009 +005010300 + +483921657 +967345821 +251876493 +548132976 +729564138 +136798245 +372689514 +814253769 +695417382 + +A well constructed Su Doku puzzle has a unique solution and can be +solved by logic, although it may be necessary to employ "guess and test" +methods in order to eliminate options (there is much contested opinion over this). +The complexity of the search determines the difficulty of the puzzle; the +example above is considered easy because it can be solved by straight +forward direct deduction. + +The 6K text file, sudoku.txt (right click and 'Save Link/Target As...'), +contains fifty different Su Doku puzzles ranging in difficulty, but all +with unique solutions (the first puzzle in the file is the example above). + +By solving all fifty puzzles find the sum of the 3-digit numbers found in +the top left corner of each solution grid; for example, 483 is the 3-digit +number found in the top left corner of the solution grid above. + +Solution: +We keep a track of the digits that are already present in each row, +column and box, and use that to check which digits can be used to fill an unfilled +cell. This process is then repeated recursively until the puzzle is solved, after +which the 3 digit numbers formed by the top left corner of each puzzle are added. + +References: +https://en.wikipedia.org/wiki/Sudoku +https://en.wikipedia.org/wiki/Backtracking +""" + +import os + + +def solve( + unfilled: list[tuple[int, int]], + row: list[int], + col: list[int], + box: list[int], + board: list[list[str]], + index: int, + total: int, +) -> bool: + """ + Recursive backtracking function to solve the sudoku + + >>> solve( + ... [(0, 0)], + ... [0b111110111, 0b111111111, 0b111111111, 0b111111111, + ... 0b111111111, 0b111111111, 0b111111111, 0b111111111, 0b111111111], + ... [0b111110111, 0b111111111, 0b111111111, 0b111111111, + ... 0b111111111, 0b111111111, 0b111111111, 0b111111111, 0b111111111], + ... [0b111110111, 0b111111111, 0b111111111, 0b111111111, + ... 0b111111111, 0b111111111, 0b111111111, 0b111111111, 0b111111111], + ... [["0","8","3","9","2","1","6","5","7"], + ... ["9","6","7","3","4","5","8","2","1"], + ... ["2","5","1","8","7","6","4","9","3"], + ... ["5","4","8","1","3","2","9","7","6"], + ... ["7","2","9","5","6","4","1","3","8"], + ... ["1","3","6","7","9","8","2","4","5"], + ... ["3","7","2","6","8","9","5","1","4"], + ... ["8","1","4","2","5","3","7","6","9"], + ... ["6","9","5","4","1","7","3","8","2"]], + ... 0, + ... 1) + True + """ + if index == total: + return True + + # Get the row and column numbers for the current unfilled cell + r, c = unfilled[index] + + for val in range(9): + # Check if value (val+1) can be placed at position (r, c) + if ( + ((row[r] & (1 << val)) == 0) + and ((col[c] & (1 << val)) == 0) + and ((box[r // 3 * 3 + c // 3] & (1 << val)) == 0) + ): + # Place the value + row[r] ^= 1 << val + col[c] ^= 1 << val + box[r // 3 * 3 + c // 3] ^= 1 << val + board[r][c] = str(val + 1) + + # Recursively solve + if solve(unfilled, row, col, box, board, index + 1, total): + return True + + # Backtrack + row[r] ^= 1 << val + col[c] ^= 1 << val + box[r // 3 * 3 + c // 3] ^= 1 << val + board[r][c] = "0" + + return False + + +def solve_sudoku(board: list[list[str]]) -> int: + """ + Solve a single sudoku puzzle and return the first 3 digits + + >>> solve_sudoku( + ... [["0","0","3","0","2","0","6","0","0"], + ... ["9","0","0","3","0","5","0","0","1"], + ... ["0","0","1","8","0","6","4","0","0"], + ... ["0","0","8","1","0","2","9","0","0"], + ... ["7","0","0","0","0","0","0","0","8"], + ... ["0","0","6","7","0","8","2","0","0"], + ... ["0","0","2","6","0","9","5","0","0"], + ... ["8","0","0","2","0","3","0","0","9"], + ... ["0","0","5","0","1","0","3","0","0"]]) + 483 + """ + unfilled = [] + row = [0] * 9 + col = [0] * 9 + box = [0] * 9 + + # Initialize the state and find unfilled positions + for i in range(0, 9, 3): + for j in range(0, 9, 3): + for ii in range(3): + for jj in range(3): + r = i + ii + c = j + jj + if board[r][c] == "0": + unfilled.append((r, c)) + else: + val = int(board[r][c]) - 1 + row[r] |= 1 << val + col[c] |= 1 << val + box[i + j // 3] |= 1 << val + + # Solve the puzzle + solve(unfilled, row, col, box, board, 0, len(unfilled)) + + # Return the first 3 digits as a number + return int(board[0][0]) * 100 + int(board[0][1]) * 10 + int(board[0][2]) + + +def solution() -> int: + """ + Finds the sum of the 3 digit numbers formed by the 3 digits in the + top left corner of the solved sudoku puzzles as described by the problem statement. + + >>> solution() + 24702 + """ + try: + script_dir = os.path.dirname(os.path.realpath(__file__)) + sudoku = os.path.join(script_dir, "sudoku.txt") + with open(sudoku) as file: + lines = file.readlines() + + except FileNotFoundError: + print("Error: Could not find sudoku.txt file") + return 0 + + res = 0 + count = 0 + board = [] + + for line in lines: + line = line.strip() + if line.startswith("G"): + continue + + board.append(list(line)) + count = (count + 1) % 9 + + if count == 0: + solution = solve_sudoku(board) + res += solution + board = [] + + return res + + +if __name__ == "__main__": + print(f"{solution()=}") diff --git a/project_euler/problem_096/sudoku.txt b/project_euler/problem_096/sudoku.txt new file mode 100644 index 000000000000..be23f6acd664 --- /dev/null +++ b/project_euler/problem_096/sudoku.txt @@ -0,0 +1,500 @@ +Grid 01 +003020600 +900305001 +001806400 +008102900 +700000008 +006708200 +002609500 +800203009 +005010300 +Grid 02 +200080300 +060070084 +030500209 +000105408 +000000000 +402706000 +301007040 +720040060 +004010003 +Grid 03 +000000907 +000420180 +000705026 +100904000 +050000040 +000507009 +920108000 +034059000 +507000000 +Grid 04 +030050040 +008010500 +460000012 +070502080 +000603000 +040109030 +250000098 +001020600 +080060020 +Grid 05 +020810740 +700003100 +090002805 +009040087 +400208003 +160030200 +302700060 +005600008 +076051090 +Grid 06 +100920000 +524010000 +000000070 +050008102 +000000000 +402700090 +060000000 +000030945 +000071006 +Grid 07 +043080250 +600000000 +000001094 +900004070 +000608000 +010200003 +820500000 +000000005 +034090710 +Grid 08 +480006902 +002008001 +900370060 +840010200 +003704100 +001060049 +020085007 +700900600 +609200018 +Grid 09 +000900002 +050123400 +030000160 +908000000 +070000090 +000000205 +091000050 +007439020 +400007000 +Grid 10 +001900003 +900700160 +030005007 +050000009 +004302600 +200000070 +600100030 +042007006 +500006800 +Grid 11 +000125400 +008400000 +420800000 +030000095 +060902010 +510000060 +000003049 +000007200 +001298000 +Grid 12 +062340750 +100005600 +570000040 +000094800 +400000006 +005830000 +030000091 +006400007 +059083260 +Grid 13 +300000000 +005009000 +200504000 +020000700 +160000058 +704310600 +000890100 +000067080 +000005437 +Grid 14 +630000000 +000500008 +005674000 +000020000 +003401020 +000000345 +000007004 +080300902 +947100080 +Grid 15 +000020040 +008035000 +000070602 +031046970 +200000000 +000501203 +049000730 +000000010 +800004000 +Grid 16 +361025900 +080960010 +400000057 +008000471 +000603000 +259000800 +740000005 +020018060 +005470329 +Grid 17 +050807020 +600010090 +702540006 +070020301 +504000908 +103080070 +900076205 +060090003 +080103040 +Grid 18 +080005000 +000003457 +000070809 +060400903 +007010500 +408007020 +901020000 +842300000 +000100080 +Grid 19 +003502900 +000040000 +106000305 +900251008 +070408030 +800763001 +308000104 +000020000 +005104800 +Grid 20 +000000000 +009805100 +051907420 +290401065 +000000000 +140508093 +026709580 +005103600 +000000000 +Grid 21 +020030090 +000907000 +900208005 +004806500 +607000208 +003102900 +800605007 +000309000 +030020050 +Grid 22 +005000006 +070009020 +000500107 +804150000 +000803000 +000092805 +907006000 +030400010 +200000600 +Grid 23 +040000050 +001943600 +009000300 +600050002 +103000506 +800020007 +005000200 +002436700 +030000040 +Grid 24 +004000000 +000030002 +390700080 +400009001 +209801307 +600200008 +010008053 +900040000 +000000800 +Grid 25 +360020089 +000361000 +000000000 +803000602 +400603007 +607000108 +000000000 +000418000 +970030014 +Grid 26 +500400060 +009000800 +640020000 +000001008 +208000501 +700500000 +000090084 +003000600 +060003002 +Grid 27 +007256400 +400000005 +010030060 +000508000 +008060200 +000107000 +030070090 +200000004 +006312700 +Grid 28 +000000000 +079050180 +800000007 +007306800 +450708096 +003502700 +700000005 +016030420 +000000000 +Grid 29 +030000080 +009000500 +007509200 +700105008 +020090030 +900402001 +004207100 +002000800 +070000090 +Grid 30 +200170603 +050000100 +000006079 +000040700 +000801000 +009050000 +310400000 +005000060 +906037002 +Grid 31 +000000080 +800701040 +040020030 +374000900 +000030000 +005000321 +010060050 +050802006 +080000000 +Grid 32 +000000085 +000210009 +960080100 +500800016 +000000000 +890006007 +009070052 +300054000 +480000000 +Grid 33 +608070502 +050608070 +002000300 +500090006 +040302050 +800050003 +005000200 +010704090 +409060701 +Grid 34 +050010040 +107000602 +000905000 +208030501 +040070020 +901080406 +000401000 +304000709 +020060010 +Grid 35 +053000790 +009753400 +100000002 +090080010 +000907000 +080030070 +500000003 +007641200 +061000940 +Grid 36 +006080300 +049070250 +000405000 +600317004 +007000800 +100826009 +000702000 +075040190 +003090600 +Grid 37 +005080700 +700204005 +320000084 +060105040 +008000500 +070803010 +450000091 +600508007 +003010600 +Grid 38 +000900800 +128006400 +070800060 +800430007 +500000009 +600079008 +090004010 +003600284 +001007000 +Grid 39 +000080000 +270000054 +095000810 +009806400 +020403060 +006905100 +017000620 +460000038 +000090000 +Grid 40 +000602000 +400050001 +085010620 +038206710 +000000000 +019407350 +026040530 +900020007 +000809000 +Grid 41 +000900002 +050123400 +030000160 +908000000 +070000090 +000000205 +091000050 +007439020 +400007000 +Grid 42 +380000000 +000400785 +009020300 +060090000 +800302009 +000040070 +001070500 +495006000 +000000092 +Grid 43 +000158000 +002060800 +030000040 +027030510 +000000000 +046080790 +050000080 +004070100 +000325000 +Grid 44 +010500200 +900001000 +002008030 +500030007 +008000500 +600080004 +040100700 +000700006 +003004050 +Grid 45 +080000040 +000469000 +400000007 +005904600 +070608030 +008502100 +900000005 +000781000 +060000010 +Grid 46 +904200007 +010000000 +000706500 +000800090 +020904060 +040002000 +001607000 +000000030 +300005702 +Grid 47 +000700800 +006000031 +040002000 +024070000 +010030080 +000060290 +000800070 +860000500 +002006000 +Grid 48 +001007090 +590080001 +030000080 +000005800 +050060020 +004100000 +080000030 +100020079 +020700400 +Grid 49 +000003017 +015009008 +060000000 +100007000 +009000200 +000500004 +000000020 +500600340 +340200000 +Grid 50 +300200000 +000107000 +706030500 +070009080 +900020004 +010800050 +009040301 +000702000 +000008006 \ No newline at end of file