123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- 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
-
-
- # return the correct node + val to try
- def genVal(domains, working, unassigned):
- heap = []
- superheap = []
- bestrating = 1.0
-
- # get the best indices according to domain size
- for i in unassigned:
- rating = domsize(domains, i) / 9.0
- if (rating == 0):
- print(i)
- 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.0
- if (rating < bestrating):
- bestrating = rating
- superheap = [i]
- elif (rating == bestrating):
- superheap.append(i)
-
- index = superheap[0]
- bestrating = 27
- val = working[index[0]][index[1]]
-
- # 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
-
- return (index, val)
- # recursive solver that uses heuristics to decide what node to explore
- def solveh(working, domains, unassigned):
- if (not unassigned):
- return working
-
- while(unassigned):
- nextThing = genVal(domains, working, unassigned)
- index = nextThing[0]
- val = nextThing[1]
- working[index[0]][index[1]] = val
- unassigned.remove(index)
- if (index == (8, 8)):
- print("value is: ", val)
-
- # check for invalidated nodes (empty domain)
- flag = True
- result = False
- newdomains = infer(domains, working, index[0], index[1], val)
- for i in range(0, 9):
- for j in range(0, 9):
- if (not domains[i][j]):
- flag = False
-
- if (flag): result = solveh(working, newdomains, copy.deepcopy(unassigned))
- if (result):
- return result
- elif (len(domains[index[0]][index[1]]) > 1):
- working[index[0]][index[1]] = 0
- domains[index[0]][index[1]].remove(val)
- unassigned.append(index)
- else:
- 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("##########")
- if (algotype == str(0)):
- result = naive(board)
- elif (algotype == str(1)):
- result = forward(board)
- elif (algotype == str(2)):
- result = heuristic(board)
- else:
- print("No valid algorithm selected. RIP.")
- print("###########")
- print(*result, sep='\n')
- print("##########")
-
- main()
|