|
@@ -0,0 +1,300 @@
|
|
|
+import sys
|
|
|
+import copy
|
|
|
+from contextlib import suppress
|
|
|
+from heapq import heappush, heappop
|
|
|
+
|
|
|
+board = sys.argv[1]
|
|
|
+algotype = sys.argv[2]
|
|
|
+
|
|
|
+# import board
|
|
|
+with open(board) as file:
|
|
|
+ board = file.read().splitlines()
|
|
|
+ board = board[:-1]
|
|
|
+
|
|
|
+# convert to list of list of ints
|
|
|
+for l in board:
|
|
|
+ board[board.index(l)] = list(map(lambda x: int(x), l.split()))
|
|
|
+
|
|
|
+# return a board that is like the board b, but has domains for each element of b (always 1-9)
|
|
|
+def genDomains(b):
|
|
|
+ for row in range(0, 9):
|
|
|
+ for cell in range(0, 9):
|
|
|
+ if (b[row][cell] == 0):
|
|
|
+ b[row][cell] = list(range(1, 10))
|
|
|
+ return b
|
|
|
+
|
|
|
+# returns True if value is valid
|
|
|
+def valid(brd, row, col, val):
|
|
|
+ # check row
|
|
|
+ if (val in brd[row]):
|
|
|
+ return False
|
|
|
+
|
|
|
+ # check column
|
|
|
+ for i in range(0, 9):
|
|
|
+ if (brd[i][col] == val):
|
|
|
+ return False
|
|
|
+
|
|
|
+ # check "box"
|
|
|
+ rownum = int(row / 3)
|
|
|
+ colnum = int(col / 3)
|
|
|
+
|
|
|
+ for i in range(rownum * 3, rownum * 3 + 3):
|
|
|
+ for j in range(colnum * 3, colnum * 3 + 3):
|
|
|
+ if (brd[i][j] == val):
|
|
|
+ return False
|
|
|
+
|
|
|
+ return True
|
|
|
+
|
|
|
+# naive backtracking solver
|
|
|
+def naive(start):
|
|
|
+ working = copy.deepcopy(start) # this is only "filled in values" and 0s
|
|
|
+ solution = genDomains(start)
|
|
|
+ # unassigned will be a list of positions we have to fill
|
|
|
+ unassigned = []
|
|
|
+ for i in range(0, 9):
|
|
|
+ for j in range(0, 9):
|
|
|
+ if (isinstance(solution[i][j], list)):
|
|
|
+ unassigned.append((i, j))
|
|
|
+
|
|
|
+ assumptions = []
|
|
|
+
|
|
|
+ if(len(unassigned) == 0):
|
|
|
+ return True
|
|
|
+
|
|
|
+ while(len(unassigned)):
|
|
|
+ index = unassigned[-1]
|
|
|
+ success = False
|
|
|
+
|
|
|
+ # iterate over all values in the domain list
|
|
|
+ while solution[index[0]][index[1]]:
|
|
|
+ i = solution[index[0]][index[1]].pop()
|
|
|
+ if (valid(working, index[0], index[1], i)):
|
|
|
+ solution[index[0]][index[1]].append(i) # keep in the domain
|
|
|
+ working[index[0]][index[1]] = i
|
|
|
+ assumptions.append(index)
|
|
|
+ unassigned.pop()
|
|
|
+ success = True
|
|
|
+ break
|
|
|
+
|
|
|
+ if (success):
|
|
|
+ continue
|
|
|
+ else:
|
|
|
+ # restore domain to full since we failed
|
|
|
+ solution[index[0]][index[1]] = list(range(1, 10))
|
|
|
+ working[index[0]][index[1]] = 0
|
|
|
+ lastdex = assumptions.pop()
|
|
|
+ solution[lastdex[0]][lastdex[1]].remove(working[lastdex[0]][lastdex[1]])
|
|
|
+ working[lastdex[0]][lastdex[1]] = 0
|
|
|
+ unassigned.append(lastdex)
|
|
|
+
|
|
|
+
|
|
|
+ if (unassigned): return False
|
|
|
+
|
|
|
+ return working
|
|
|
+
|
|
|
+
|
|
|
+# returns a board (domains) where inferences are made for the cell at row, col
|
|
|
+def infer(solutions, brd, row, col, val):
|
|
|
+ domains = copy.deepcopy(solutions)
|
|
|
+ # remove from same row & col
|
|
|
+ for i in range(0, 9):
|
|
|
+ if (val in domains[row][i] and i != col):
|
|
|
+ domains[row][i].remove(val)
|
|
|
+ if (val in domains[i][col] and i != row):
|
|
|
+ domains[i][col].remove(val)
|
|
|
+
|
|
|
+ # remove for "box"
|
|
|
+ rownum = int(row / 3)
|
|
|
+ colnum = int(col / 3)
|
|
|
+
|
|
|
+ for i in range(rownum * 3, rownum * 3 + 3):
|
|
|
+ for j in range(colnum * 3, colnum * 3 + 3):
|
|
|
+ if (val in domains[i][j] and (i != row and j != col)):
|
|
|
+ domains[i][j].remove(val)
|
|
|
+
|
|
|
+ return domains
|
|
|
+
|
|
|
+
|
|
|
+# generates domains in a format supporting forward checking
|
|
|
+def gen2Domains(b):
|
|
|
+ for row in range(0, 9):
|
|
|
+ for cell in range(0, 9):
|
|
|
+ if (b[row][cell] == 0):
|
|
|
+ b[row][cell] = list(range(1, 10))
|
|
|
+ else:
|
|
|
+ b[row][cell] = [b[row][cell]]
|
|
|
+ return b
|
|
|
+
|
|
|
+
|
|
|
+# recursive solver for forward-checking
|
|
|
+def solve(working, domains, unassigned):
|
|
|
+ if (not unassigned):
|
|
|
+ return working
|
|
|
+
|
|
|
+ index = unassigned.pop()
|
|
|
+
|
|
|
+ for i in domains[index[0]][index[1]]:
|
|
|
+ working[index[0]][index[1]] = i
|
|
|
+ newdomains = infer(domains, working, index[0], index[1], i)
|
|
|
+ result = solve(working, newdomains, copy.deepcopy(unassigned))
|
|
|
+ if (result):
|
|
|
+ return result
|
|
|
+ else:
|
|
|
+ continue
|
|
|
+
|
|
|
+ return False
|
|
|
+
|
|
|
+# forward checking solver
|
|
|
+def forward(start):
|
|
|
+ working = copy.deepcopy(start) # this is only "filled in values" and 0s
|
|
|
+ domains = gen2Domains(start)
|
|
|
+ # unassigned will be a list of positions we have to fill
|
|
|
+ unassigned = []
|
|
|
+ for i in range(0, 9):
|
|
|
+ for j in range(0, 9):
|
|
|
+ if (len(domains[i][j]) == 9):
|
|
|
+ unassigned.append((i, j))
|
|
|
+
|
|
|
+ for i in range(0, 9):
|
|
|
+ for j in range(0, 9):
|
|
|
+ if (working[i][j] != 0):
|
|
|
+ domains = infer(domains, working, i, j, working[i][j])
|
|
|
+
|
|
|
+ return solve(working, domains, unassigned)
|
|
|
+
|
|
|
+
|
|
|
+# returns size of domain for a given index
|
|
|
+def domsize(domains, index):
|
|
|
+ return (len(domains[index[0]][index[1]]))
|
|
|
+
|
|
|
+
|
|
|
+# returns the # of 0s that are in the same row, col, or box as index
|
|
|
+def related(brd, index):
|
|
|
+ count = 0
|
|
|
+ # count 0s in row + col
|
|
|
+ for i in range(0, 9):
|
|
|
+ if (brd[index[0]][i] == 0 and i != index[1]):
|
|
|
+ ++count
|
|
|
+ if (brd[i][index[1]] == 0 and i != index[0]):
|
|
|
+ ++count
|
|
|
+
|
|
|
+ # count for "box" as well
|
|
|
+ rownum = int(index[0] / 3)
|
|
|
+ colnum = int(index[1] / 3)
|
|
|
+
|
|
|
+ for i in range(rownum * 3, rownum * 3 + 3):
|
|
|
+ for j in range(colnum * 3, colnum * 3 + 3):
|
|
|
+ if (brd[i][j] == 0 and (i != index[0] and j != index[1])):
|
|
|
+ ++count
|
|
|
+
|
|
|
+ return count
|
|
|
+
|
|
|
+
|
|
|
+# returns the # of constraints that will follow from assigning index with val
|
|
|
+def lcv(solutions, index, val):
|
|
|
+ count = 0
|
|
|
+ # count 0s in row + col
|
|
|
+ for i in range(0, 9):
|
|
|
+ if (val in solutions[index[0]][i] and i != index[1]):
|
|
|
+ ++count
|
|
|
+ if (val in solutions[i][index[1]] and i != index[0]):
|
|
|
+ ++count
|
|
|
+
|
|
|
+ # count for "box" as well
|
|
|
+ rownum = int(index[0] / 3)
|
|
|
+ colnum = int(index[1] / 3)
|
|
|
+
|
|
|
+ for i in range(rownum * 3, rownum * 3 + 3):
|
|
|
+ for j in range(colnum * 3, colnum * 3 + 3):
|
|
|
+ if (val in solutions[i][j] and (i != index[0] and j != index[1])):
|
|
|
+ ++count
|
|
|
+
|
|
|
+ return count
|
|
|
+
|
|
|
+# recursive solver that uses heuristics to decide what node to explore
|
|
|
+def solveh(working, domains, unassigned):
|
|
|
+ if (not unassigned):
|
|
|
+ return working
|
|
|
+
|
|
|
+ # heap and superheap used to record indices that are the "best" according to the heuristics
|
|
|
+ heap = []
|
|
|
+ superheap = []
|
|
|
+ bestrating = 1.0
|
|
|
+
|
|
|
+ # get the best indices according to domain size
|
|
|
+ for i in unassigned:
|
|
|
+ rating = domsize(domains, i) / 9
|
|
|
+ if (rating < bestrating):
|
|
|
+ bestrating = rating
|
|
|
+ heap = [i]
|
|
|
+ elif (rating == bestrating):
|
|
|
+ heap.append(i)
|
|
|
+
|
|
|
+ # get the best indices according to degree(related cells)
|
|
|
+ bestrating = 1
|
|
|
+ for i in heap:
|
|
|
+ rating = related(working, i) / 27
|
|
|
+ if (rating < bestrating):
|
|
|
+ bestrating = rating
|
|
|
+ superheap = [i]
|
|
|
+ elif (rating == bestrating):
|
|
|
+ superheap.append(i)
|
|
|
+
|
|
|
+ index = superheap[0]
|
|
|
+ bestrating = 27
|
|
|
+ val = 0
|
|
|
+
|
|
|
+ # get best values according to LCV
|
|
|
+ for i in domains[index[0]][index[1]]:
|
|
|
+ rating = lcv(domains, index, i)
|
|
|
+ if (rating <= bestrating):
|
|
|
+ bestrating = rating
|
|
|
+ val = i
|
|
|
+
|
|
|
+ working[index[0]][index[1]] = val
|
|
|
+ unassigned.remove(index)
|
|
|
+
|
|
|
+ newdomains = infer(copy.deepcopy(domains), copy.deepcopy(working), index[0], index[1], val)
|
|
|
+ result = solveh(copy.deepcopy(working), copy.deepcopy(newdomains), copy.deepcopy(unassigned))
|
|
|
+ if (result):
|
|
|
+ return result
|
|
|
+ elif (domains[index[0]][index[1]]):
|
|
|
+ working[index[0]][index[1]] = 0
|
|
|
+ domains[index[0]][index[1]].remove(val)
|
|
|
+ unassigned.append(index)
|
|
|
+ result = solveh(copy.deepcopy(working), copy.deepcopy(domains), copy.deepcopy(unassigned))
|
|
|
+ if (result):
|
|
|
+ return result
|
|
|
+
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
+# forward checking solver with heuristics
|
|
|
+def heuristic(start):
|
|
|
+ working = copy.deepcopy(start) # this is only "filled in values" and 0s
|
|
|
+ domains = gen2Domains(start)
|
|
|
+ # unassigned will be a list of positions we have to fill
|
|
|
+ unassigned = []
|
|
|
+ for i in range(0, 9):
|
|
|
+ for j in range(0, 9):
|
|
|
+ if (len(domains[i][j]) == 9):
|
|
|
+ unassigned.append((i, j))
|
|
|
+
|
|
|
+ for i in range(0, 9):
|
|
|
+ for j in range(0, 9):
|
|
|
+ if (working[i][j] != 0):
|
|
|
+ domains = infer(domains, working, i, j, working[i][j])
|
|
|
+
|
|
|
+ return solveh(working, domains, unassigned)
|
|
|
+
|
|
|
+
|
|
|
+def main():
|
|
|
+ print("###########")
|
|
|
+ print(*board, sep='\n')
|
|
|
+ print("##########")
|
|
|
+ result = heuristic(board)
|
|
|
+ print("###########")
|
|
|
+ print(*result, sep='\n')
|
|
|
+ print("##########")
|
|
|
+
|
|
|
+main()
|