096 Su Doku.sf 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. #!/usr/bin/ruby
  2. # Author: Trizen
  3. # Date: 12 February 2024
  4. # https://github.com/trizen
  5. # https://projecteuler.net/problem=96
  6. # Runtime: 31.915s
  7. func is_valid(board, row, col, num) {
  8. # Check if the number is not present in the current row and column
  9. for i in ^9 {
  10. if ((board[row][i] == num) || (board[i][col] == num)) {
  11. return false
  12. }
  13. }
  14. # Check if the number is not present in the current 3x3 subgrid
  15. var (start_row, start_col) = (3*idiv(row, 3), 3*idiv(col, 3))
  16. for i in ^3, j in ^3 {
  17. if (board[start_row + i][start_col + j] == num) {
  18. return false
  19. }
  20. }
  21. return true
  22. }
  23. func find_empty_locations(board) {
  24. var locations = []
  25. # Find all empty positions (cells with 0)
  26. for i in ^9, j in ^9 {
  27. if (board[i][j] == 0) {
  28. locations << [i, j]
  29. }
  30. }
  31. return locations
  32. }
  33. func find_empty_location(board) {
  34. # Find an empty position (cell with 0)
  35. for i in ^9, j in ^9 {
  36. if (board[i][j] == 0) {
  37. return (i, j)
  38. }
  39. }
  40. return (nil, nil) # If the board is filled
  41. }
  42. func solve_sudoku_fallback(board) {
  43. var (row, col) = find_empty_location(board)
  44. if (!defined(row) && !defined(col)) {
  45. return true # Puzzle is solved
  46. }
  47. for num in (1..9) {
  48. if (is_valid(board, row, col, num)) {
  49. # Try placing the number
  50. board[row][col] = num
  51. # Recursively try to solve the rest of the puzzle
  52. if (__FUNC__(board)) {
  53. return true
  54. }
  55. # If placing the current number doesn't lead to a solution, backtrack
  56. board[row][col] = 0
  57. }
  58. }
  59. return false # No solution found
  60. }
  61. func solve_sudoku(board) {
  62. loop {
  63. # Return early when the first 3 values are solved
  64. if ((board[0][0] != 0) && (board[0][1] != 0) && (board[0][2] != 0)) {
  65. return board
  66. }
  67. var empty_locations = find_empty_locations(board) || break
  68. var found = false
  69. # Solve easy cases
  70. for i,j in empty_locations {
  71. var(count=0, value=0)
  72. for n in (1..9) {
  73. is_valid(board, i, j, n) || next
  74. break if (++count > 1)
  75. value = n
  76. }
  77. if (count == 1) {
  78. board[i][j] = value
  79. found ||= true
  80. }
  81. }
  82. next if found
  83. # Solve more complex cases
  84. var stats = []
  85. for i,j in empty_locations {
  86. stats[i][j] = (1..9 -> grep{|n| is_valid(board, i, j, n) })
  87. }
  88. var cols = []
  89. var rows = []
  90. var subgrid = []
  91. for i,j in empty_locations {
  92. stats[i][j].each {|v|
  93. cols[j][v] := 0 ++
  94. rows[i][v] := 0 ++
  95. subgrid[3*idiv(i,3)][3*idiv(j,3)][v] := 0 ++
  96. }
  97. }
  98. for i,j in empty_locations {
  99. stats[i][j].each {|v|
  100. if ((cols[j][v] == 1) ||
  101. (rows[i][v] == 1) ||
  102. (subgrid[3*idiv(i,3)][3*idiv(j,3)][v] == 1)
  103. ) {
  104. board[i][j] = v
  105. found ||= true
  106. }
  107. }
  108. }
  109. next if found
  110. # Give up and try brute-force
  111. say "Fallback: #{empty_locations.len}"
  112. solve_sudoku_fallback(board)
  113. return board
  114. }
  115. return board
  116. }
  117. var grids = File(ARGV[0] \\ "p096_sudoku.txt").open_r.grep(/^[0-9]+$/)
  118. var sum = 0
  119. while (grids) {
  120. var grid = grids.splice(0,9).map{ .chars.map{.to_i} }
  121. var solution = solve_sudoku(grid)
  122. sum += Num(join('', solution[0][0,1,2]))
  123. }
  124. say sum