-- dbg = require ("debugger") -- dbg.auto_where = 2 function table2string(t) local myself = table2string local l = {} local n = 0 for k, v in pairs(t) do n = n + 1 if type(v) == "table" then l[n] = k .. ': ' .. myself(v) elseif type(v) == "string" then l[n] = k .. ': ' .. '"' .. v .. '"' else l[n] = k .. ': ' .. v end end return "{" .. table.concat(l, ", ") .. "}" end function find(array, element) local index = 0 for i = 1, #array do if array[i] == element then index = i break end end return index end function createEmptyBoard() local b = {} for i = 1, 9 do b[i] = {} for j = 1, 9 do b[i][j] = 0 end end return b end function cloneBoard(b) local c = {} for i = 1, 9 do c[i] = {} for j = 1, 9 do c[i][j] = b[i][j] end end return c end function loadBoard(fn) local boards = {} local r = 0 local board = {} for line in io.lines(fn) do if line:gsub("%s+", "") == "" then table.insert(boards, board) board = {} r = 0 else r = r + 1 local c = 0 board[r] = {} for item in line:gmatch("%w+") do c = c + 1 board[r][c] = tonumber(item) end end end table.insert(boards, board) return boards end function showBoard(board) for row = 1, 9 do print(table.concat(board[row], " ")) end end function iterZones(board) local row = 0 local column = 0 local block = 0 return function() local a = {} local p = {} if row < 9 then row = row + 1 for i = 1, 9 do a[i] = board[row][i] p[i] = {row, i} end return a, p end if column < 9 then column = column + 1 for i = 1, 9 do a[i] = board[i][column] p[i] = {i, column} end return a, p end if block < 9 then block = block + 1 local dx = math.floor((block - 1) / 3) local dy = (block - 1) % 3 local n = 0 for i = 1, 3 do for j = 1, 3 do local x = i + 3 * dx local y = j + 3 * dy n = n + 1 a[n] = board[x][y] p[n] = {x, y} end end return a, p end end end function checkAUX(zone, pos) local isComplete = true local ok = true local conflictList = {} local n = 0 for i = 1, 8 do for j = i+1, 9 do if zone[i] == 0 then isComplete = false elseif zone[i] == zone[j] then ok = false n = n + 1 conflictList[n] = {x1=pos[i][1], y1=pos[i][2], x2=pos[j][1], y2=pos[j][2]} end end end return ok, isComplete, conflictList end function checkBoard(board) local ok = true local isComplete = true local conflictList = {} for zone, pos in iterZones(board) do local tOk, tIsComplete, tConflictList = checkAUX(zone, pos) if not tOk then ok = false end if not tIsComplete then isComplete = false end for _, conflict in ipairs(tConflictList) do table.insert(conflictList, conflict) end end if not ok then isComplete = false end return ok, isComplete, conflictList end function calCellValidOptions(board, x, y) if board[x][y] ~= 0 then return {} end local u = {} local v = {} for i = 1, 9 do if board[x][i] ~= 0 then table.insert(u, board[x][i]) end if board[i][y] ~= 0 then table.insert(u, board[i][y]) end end local dx = math.floor((x - 1) / 3) * 3 local dy = math.floor((y - 1) / 3) * 3 for i = 1, 3 do for j = 1, 3 do local a = board[i+dx][j+dy] if a ~= 0 then table.insert(u, a) end end end for i = 1, 9 do local found = false for _, elm in ipairs(u) do if elm == i then found = true; break end end if not found then table.insert(v, i) end end return v end function buildSearchSpace(board) local s = {} for i = 1, 9 do s[i] = {} for j = 1, 9 do s[i][j] = calCellValidOptions(board, i, j) end end return s end function findPairsIndSearchSpace(board, searchSpace) local r = {} local zoneIndex = 0 for zone, pos in iterZones(board) do zoneIndex = zoneIndex + 1 for i = 1, 8 do local x1 = pos[i][1] local y1 = pos[i][2] local s1 = searchSpace[x1][y1] if #s1 == 2 then for j = i + 1, 9 do local x2 = pos[j][1] local y2 = pos[j][2] local s2 = searchSpace[x2][y2] if #s2 == 2 then if s1[1] == s2[1] and s1[2] == s2[2] then -- if zoneIndex <= 18 or x1 ~= x2 and y1 ~= y2 then table.insert(r, {zoneIndex=zoneIndex, cells={i, j}, pair=s1}) -- print("----------") -- print("zone: ", zoneIndex) -- print(table2string(zone)) -- print(i, j) -- print(table2string(s1)) -- end end end end end end end return r end function showSearchSpace(s) local hl = ("+-------+-------+-------+-------+-------+-------+-------+-------+-------+") print(hl) for i = 1, 27 do local x = math.floor((i - 1) / 3) + 1 local a = (i - 1) % 3 local row = {} for j = 1, 27 do local y = math.floor((j - 1) / 3) + 1 local k = ((j - 1) % 3 + 1) + 3 * a local found = false for _, m in ipairs(s[x][y]) do if m == k then found = true; break end end if found then table.insert(row, k) else table.insert(row, " ") end if j % 3 == 0 then table.insert(row, "|") end end print("| "..table.concat(row, " ")) if i % 3 == 0 then print(hl) end end end function getZoneByIndex(board, zoneIndex) local index = (zoneIndex - 1) % 9 + 1 local block = math.floor((zoneIndex - 1) / 9) + 1 local a = {} local p = {} if block == 1 then for i = 1, 9 do a[i] = board[index][i] p[i] = {index, i} end elseif block == 2 then for i = 1, 9 do a[i] = board[i][index] p[i] = {index, i} end elseif block == 3 then local dx = math.floor((index - 1) / 3) local dy = (index - 1) % 3 local n = 0 for i = 1, 3 do for j = 1, 3 do local x = i + 3 * dx local y = j + 3 * dy n = n + 1 a[n] = board[x][y] p[n] = {x, y} end end end return a, p end function getZonePosByIndex(zoneIndex) local index = (zoneIndex - 1) % 9 + 1 local block = math.floor((zoneIndex - 1) / 9) + 1 local p = {} if block == 1 then for i = 1, 9 do p[i] = {index, i} end elseif block == 2 then for i = 1, 9 do p[i] = {index, i} end elseif block == 3 then local dx = math.floor((index - 1) / 3) local dy = (index - 1) % 3 local n = 0 for i = 1, 3 do for j = 1, 3 do local x = i + 3 * dx local y = j + 3 * dy n = n + 1 p[n] = {x, y} end end end return p end function iterZonePos(zoneIndex) local p = getZonePosByIndex(zoneIndex) local i = 0 return function() if i < 9 then i = i + 1 return p[i][1], p[i][2] end end end function fillSingles(board, searchSpace) local count = 0 for i = 1, 9 do for j = 1, 9 do if #searchSpace[i][j] == 1 then board[i][j] = searchSpace[i][j][1] count = count + 1 end end end return count end function applyFillSinglesRepeatedly(board, s) local s = s or buildSearchSpace(board) local count = fillSingles(board, s) local total = count while count ~= 0 do s = buildSearchSpace(board) count = fillSingles(board, s) total = total + count end return total end function eliminatePairsInSearchSpaceAUX(board, searchSpace, pair, zoneIndex, cells) local _, pos = getZoneByIndex(board, zoneIndex) local count = 0 for i = 1, 9 do if i ~= cells[1] and i ~= cells[2] then local x = pos[i][1] local y = pos[i][2] local del = {} for index, v in ipairs(searchSpace[x][y]) do if v == pair[1] or v == pair[2] then count = count + 1 table.insert(del, index) end end for _, index in ipairs(del) do table.remove(searchSpace[x][y], index) end end end return count end function eliminatePairsInSearchSpace(board, searchSpace) local p = findPairsIndSearchSpace(board, searchSpace) local count = 0 for _, m in ipairs(p) do count = count + eliminatePairsInSearchSpaceAUX(board, searchSpace, m.pair, m.zoneIndex, m.cells) end return count end function allPairs(board) local s = buildSearchSpace(board) applyFillSinglesRepeatedly(board, s) eliminatePairsInSearchSpace(board, s) local count = applyFillSinglesRepeatedly(board, s) while count ~= 0 do -- print("--------------------------------------------------") -- showBoard(board) -- showSearchSpace(s) -- print(count) -- io.read(1) s = buildSearchSpace(board) eliminatePairsInSearchSpace(board, s) count = applyFillSinglesRepeatedly(board, s) end end function findFirstEmptyCell(board) for i = 1, 9 do for j = 1, 9 do if board[i][j] == 0 then return i, j end end end end function checkSearchSpace(board, searchSpace) local s = searchSpace or buildSearchSpace(board) for i = 1, 9 do for j = 1, 9 do if board[i][j] == 0 then if #s[i][j] == 0 then return false end end end end return true end function backTrace2(board, s) local s = s or buildSearchSpace(board) local x, y = findFirstEmptyCell(board) for _, v in ipairs(s[x][y]) do local b = cloneBoard(board) b[x][y] = v allPairs(b) local ok, finished = checkBoard(b) if finished then return b end if ok and checkSearchSpace(b) then local r = backTrace(b) if r then return r end end end end function backTrace3(board, tried) -- dbg() local tried = tried or createEmptyBoard() for x = 1, 9 do for y = 1, 9 do if tried[x][y] == 0 and board[x][y] == 0 then tried[x][y] = 1 print(x, y) local s = buildSearchSpace(board) for _, v in ipairs(s[x][y]) do local b = cloneBoard(board) b[x][y] = v allPairs(b) local ok, finished = checkBoard(b) if finished then return b end if ok and checkSearchSpace(b) then local r = backTrace(b, tried) if r then return r end end end end end end end function backTrace4(board, cx, cy) local s = buildSearchSpace(board) for x = 1, 9 do for y = 1, 9 do if x ~= cx and y ~=cy then for _, v in ipairs(s[x][y]) do local b= cloneBoard(board) b[x][y] = v print(x, y, v) -- allPairs(b) applyFillSinglesRepeatedly(b) local ok, finished = checkBoard(b) if finished then return b end if ok and checkSearchSpace(b) then local r = backTrace(b, x, y) if r then return r end end end end end end end function backTrace(board) local s = buildSearchSpace(board) for x = 1, 9 do for y = 1, 9 do for _, v in ipairs(s[x][y]) do local b= cloneBoard(board) b[x][y] = v -- allPairs(b) applyFillSinglesRepeatedly(b) local ok, finished = checkBoard(b) print(x, y, v, ok, finished) showBoard(b) io.read(1) if finished then return b end if ok and checkSearchSpace(b) then local r = backTrace(b, x, y) if r then return r end end end return end end end function solve(board) -- allPairs(board) applyFillSinglesRepeatedly(board) local ok, finished = checkBoard(board) if not ok then print("something's wrong!"); return end if finished then showBoard(board); return end local r = backTrace(board) if r then showBoard(r) else print "Could not solve!" end end function sHelper(n) local fn = string.format("/home/reza/p/lua/22.sudoku/boards/%03d.txt", n) local board = loadBoard(fn)[1] solve(board) end function checkBoardsEqual(b1, b2) for i = 1, 9 do for j = 1, 9 do if b1[i][j] ~= b2[i][j] then return false end end end return true end -- b = loadBoard("b.txt")[1] -- applyFillSinglesRepeatedly(b) -- s = buildSearchSpace(b) -- allPairs(b) return { loadBoard = loadBoard, cloneBoard = cloneBoard, createEmptyBoard = createEmptyBoard, calCellValidOptions = calCellValidOptions, checkBoard = checkBoard, applyFillSinglesRepeatedly = applyFillSinglesRepeatedly, showBoard = showBoard, checkBoardsEqual = checkBoardsEqual, }