diff --git a/sudoku/sudoku.lua b/sudoku/sudoku.lua index 1cd5217..344d2bf 100644 --- a/sudoku/sudoku.lua +++ b/sudoku/sudoku.lua @@ -1,8 +1,86 @@ -local utils = require "sudoku.utils" +-- dbg = require ("debugger") +-- dbg.auto_where = 2 -local sudoku = {} +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 sudoku.iterZones(board) +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 @@ -44,7 +122,7 @@ function sudoku.iterZones(board) end end -local function checkAUX(zone, pos) +function checkAUX(zone, pos) local isComplete = true local ok = true local conflictList = {} @@ -63,11 +141,11 @@ local function checkAUX(zone, pos) return ok, isComplete, conflictList end -function sudoku.checkBoard(board) +function checkBoard(board) local ok = true local isComplete = true local conflictList = {} - for zone, pos in sudoku.iterZones(board) do + 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 @@ -75,10 +153,11 @@ function sudoku.checkBoard(board) table.insert(conflictList, conflict) end end + if not ok then isComplete = false end return ok, isComplete, conflictList end -function sudoku.calCellValidOptions(board, x, y) +function calCellValidOptions(board, x, y) if board[x][y] ~= 0 then return {} end local u = {} local v = {} @@ -104,19 +183,360 @@ function sudoku.calCellValidOptions(board, x, y) return v end -function sudoku.buildSearchSpace(board) +function buildSearchSpace(board) local s = {} for i = 1, 9 do s[i] = {} for j = 1, 9 do - s[i][j] = sudoku.calCellValidOptions(board, i, j) + s[i][j] = calCellValidOptions(board, i, j) end end return s end -sudoku.cloneBoard = utils.cloneBoard -sudoku.createEmptyBoard = utils.createEmptyBoard -sudoku.loadBoard = utils.loadBoard -return sudoku +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 + +-- b = loadBoard("b.txt")[1] +-- applyFillSinglesRepeatedly(b) +-- s = buildSearchSpace(b) +-- allPairs(b) + + +return { + loadBoard = loadBoard, + calCellValidOptions = calCellValidOptions, + cloneBoard = cloneBoard, + checkBoard = checkBoard, + createEmptyBoard = createEmptyBoard, +} diff --git a/sudoku/utils.lua b/sudoku/utils.lua deleted file mode 100644 index e9937b6..0000000 --- a/sudoku/utils.lua +++ /dev/null @@ -1,84 +0,0 @@ -local 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 - -local 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 - -local 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 - -local 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 - -local 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 - -local function showBoard(board) - for row = 1, 9 do - print(table.concat(board[row], " ")) - end -end - -return { - createEmptyBoard = createEmptyBoard, - cloneBoard = cloneBoard, - loadBoard = loadBoard, -}