# SUDOKU CODE CHALLENGES - given to keep us all on the same page(s)
# John Oakey ver: 120921
# VALUES AND CONTAINERS for these challenges - the following lines are given:
# An Example New Sudoku Board - list of rows with each element
# of a row holding integers for given, or guessed values. The
# row elements are columns in order from zero to nine - r zero is not used
# but is defined so that column numbers are logical to the programmer.
r0=[0, 0,0,0,0,0,0,0,0,0] # There are a lot of ways to structure this data, the most
# efficient being a dictionary holding 10 elements,
r1=[0, 1,0,0,0,7,0,5,8,3] # with each element being a corresponding row with
r2=[0, 0,2,0,9,0,0,6,0,0] # it's data being a list of values by ordered column.
r3=[0, 0,8,0,0,3,1,0,2,0] # BUT... by doing it as at left it is VASTLY easier to
r4=[0, 0,0,0,4,1,0,0,0,0] # enter, check and visualize a puzzle.
r5=[0, 0,0,8,0,0,0,4,0,0] # We "throw away" r0 so when we want to access the first
r6=[0, 0,0,0,0,8,6,0,0,0] # row we can slice [1] instead of [0]
r7=[0, 0,7,0,5,9,0,0,6,0] # <-- ok, this is an actual sudoku puzzle board
r8=[0, 0,0,3,0,0,7,0,9,0]
r9=[0, 9,1,5,0,6,0,0,0,7]
pvr0=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]] # Note: we could create the
# "pvr" (possible value row) lists of lists with:
pvr1=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]] # bl = "=[[0],[0],[0],[0],[0],[0],[0],[0],[0],[0]]"
pvr2=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]] # for i in range(10):
pvr3=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]] # exec("pvr" + str(i) + bl)
pvr4=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]] # but by printing it out as at left a beginner
pvr5=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]] # can better visualize the data structure
pvr6=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]]
pvr7=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]]
pvr8=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]]
pvr9=[[0], [0],[0],[0],[0],[0],[0],[0],[0],[0]]
# Create a tuple to hold given/guessed rows r0-r9 and one for our all possible value lists
t_posted = (r0,r1,r2,r3,r4,r5,r6,r7,r8,r9) # a tuple whose elements are lists
t_all_pcv = (pvr0,pvr1,pvr2,pvr3,pvr4,pvr5,pvr6,pvr7,pvr8,pvr9) # pvr = possible values row
pvlist = [] # a reuseable temporary list to hold possible values
fs_all_values = frozenset([1,2,3,4,5,6,7,8,9]) # all valid sudoku values in frozen set,
# now a tuple to hold 10 "sub-tuples" with stored box corners
toft_box_corners=((0,0),(1,1),(1,4),(1,7),(4,1),(4,4),(4,7),(7,1),(7,4),(7,7)) # toft = tuple of tuples
# __________FUNCTIONS______________________________
# function to print given/guessed Sudoku board data
def print_board():
print("Columns",end = "") # first print the columns title
for col in range(1,10): # then the header for each column
# for more on f string formatting see Big Daddy's
# Formatting Options toolbox
print((f"***---{ str(col):}---***").center(15),end="")
print()
for row in range(1,10): # for every row
print(f"Row{str(row)}: ", end="") # we print the title and then
for col in range(1,10): # for every column in the row
x = str(t_posted[row][col]) # we print the t_posted value
print(f" | {x:^12}",end="")
print("|")
print(" "*7 + "-"*135)
##
# function to print possible values in cells by row and column
def print_pv_board(): # Note: we do not call board_pv to update
print("Columns",end="") # because it would erase any results from identifying and
for col in range(1,10): # removing impossible numbers revealed by matched pairs
print((f"???---{ str(col):}---???").center(15),end="")
print() # this will NOT produce a neat alligned print out because
for row in range(1,10): # there are highly variable lengths to the apv lists
print(f"Row{str(row)}: ", end="")
for col in range(1,10):
x = str(t_all_pcv[row][col])
print(f" | {x:^12}",end="")
print("|")
print(" "*7 + "-"*135)
# function to find box number given row and column
def box_number(row, col):
box = ((0,1,4,7)[(row-1)//3 + 1]) + ((col-1)//3) # quick way to find box number
return(box)
'''This line can use some explaination - think of it like this:
rsval = (0,1,4,7) # 0 is throw away placeholder; 1,4,7 are base row values
rstack = (row-1)//3 +1 # using floor division gives an index value in groups of 3
csec = (col-1)//3 # floor div gives val to add to row base
box = rsval[rstack] + csec # add the col idex value to the row index giving box 1-9 '''
boxnum = lambda row, col:((0,1,4,7)[(row-1)//3 + 1]) + ((col-1)//3) #lambda is an unnamed inline function
#-----
# function to find single numbers in row(row)
def nums_in_a_row(row):
rnums = []
for col in range(1,10):
if t_posted[row][col]>0:
rnums.append(t_posted[row][col])
return(sorted(rnums))
# function to find single numbers in col(col)
def nums_in_a_col(col):
cnums = []
for row in range(1,10):
if t_posted[row][col] > 0:
cnums.append(t_posted[row][col])
return(sorted(cnums))
# function to find single numbers in a box(b)
def nums_in_a_box(row, col): # given any row and column
boxnums = []
box = box_number(row, col)
for r in range(toft_box_corners[box][0], (toft_box_corners[box][0])+3):
for c in range(toft_box_corners[box][1], (toft_box_corners[box][1])+3):
if t_posted[r][c] > 0:
boxnums.append(t_posted[r][c])
s_boxnums = set(boxnums)
return(sorted(list(s_boxnums)))
#-----
# function to find POSSIBLE VALUES for a given row and column CELL
# first find used values in row, col and box for a single r/c cell
# then find inverse set using the .difference operation vs fs_all-values
def cell_pv(row, col):
if t_posted[row][col] > 0:
return([t_posted[row][col]])
else: #a set is ideal for this purpose, it does not allow duplicates
used_nums = set(nums_in_a_row(row)) # make a place to put used numbers
used_nums.update(nums_in_a_col(col)) # using a set eliminates duplicates
used_nums.update(nums_in_a_box(row,col)) # from the row/col/box function but we
return(sorted(list(fs_all_values.difference(used_nums)))) # must return a sorted list
# function to compile ALL POSSIBLE VALUES in all cells of a board
def board_pv():
for row in range(1,10):
for col in range(1,10):
t_all_pcv[row][col] = cell_pv(row, col)
return(t_all_pcv)
#-----------
# function to post a guess
def post(row, col, guess):
t_posted[row][col]= guess
# COLUMN function to find & remove numbers in matched pairs
def mpair_cols(col):
global to_remove
to_remove=[]
to_remove.clear()
popped_list =[]
pv_list = []
for row in range(1,10):
pv_list.append(t_all_pcv[row][col])
# create "pairs" with a list comprehension - MUST sort pairs or comparisons will not work later
pairs = [sorted(item) for item in pv_list if len(item) == 2]
if len(pairs) < 2: # if 0 or 1 pair we can't have a matching set
return [] # so return an empty list
else: # ok, we know there are 2 to 8 pairs - whatever the length of pairs list
s_nums2delete = set()
for x in range(len(pairs)): # for however many pairs we found
if pairs.count(pairs[x]) == 2: # if there are two identical pairs
s_nums2delete.add(pairs[x][0]) # try to put each of it's values
s_nums2delete.add(pairs[x][1]) # in a set - the set will refuse duplicates
to_remove =sorted(list(s_nums2delete)) # convert the filtered set to a list
if to_remove == []: # if there are no values to remove we're done
return[]
else:
for row in range(1,10): # otherwise we need to remove them from t_all_pcv
if to_remove == t_all_pcv[row][col]: # except don't remove #'s from our matched pairs
continue
elif to_remove[0] in t_all_pcv[row][col]: # look at zero and if it is there look at one
popped_list.append(t_all_pcv[row][col].pop(t_all_pcv[row][col].index(to_remove[0])))
if to_remove[1] in t_all_pcv[row][col]:
popped_list.append(t_all_pcv[row][col].pop(t_all_pcv[row][col].index(to_remove[1])))
elif to_remove[1] in t_all_pcv[row][col]: # otherwise just check out one
popped_list.append(t_all_pcv[row][col].pop(t_all_pcv[row][col].index(to_remove[1])))
return(popped_list)
#________________________TESTS_______________________________________________
# test1 printing board
print_board()
#------------
# begin with a pair of "random" values to find for tests (box is calculated later):
row, col = 5,4
print("\n" + "Testing functions to find used numbers in row " +str(row) + ", col " + str(col) + ", and calculated box " + str(boxnum(row, col)))
print("the box number named function yeilds: "+ str(box_number(row,col))) # this line if you are testing the named function
print("the box number from a lambda yeilds: "+ str(boxnum(row,col))) # this line if you are testing the lambda version
# get given/single numbers in row r
rnums = nums_in_a_row(row)
print("row "+str(row) + ": "+ str(rnums))
# get given/single numbers in col c
cnums = nums_in_a_col(col)
print("col " + str(col) + ": "+ str(cnums))
# get given/single numbers in row/col box
boxnums=[]
b = box_number(row, col)
boxnums = nums_in_a_box(row, col)
print ("box #" + str(b), end = " ")
print("boxnums are: " + str(boxnums) + "\n")
#------------
# test getting possible values for a cell r,c
print("Test function to find possible values for a cell")
pvlist.clear()
pvlist = (cell_pv(row,col))
print("for row "+str(row)+ " and col "+str(col), end=": ")
print(pvlist, end=" ")
print("This is the single value or the inverse of the list of combined row, col and box values.\n")
print("And here are all the possible values by row and column.\n")
board_pv()
print_pv_board()
# test posting a value to the posted and given values in t_posted
print("\nTesting posting a value - but cheating a little to make it a value")
print("that will yeild a matching pair in column four.\n")
row, col, guess = 1, 4, 2
post(row, col, guess)
print("We have a new board of possible values. Note many 2's are gone - except our post.")
print("Notice the 3 & 7 pairs in column 4. But an impossible 3 remains at R9C4,")
board_pv()
print_pv_board()
# testing finding and removing matched pair values from a column - this demo is for col 4 only
# Note this does not change the results you get when refreshing possible values with
# board_pv since that would destroy our "corrections" of removing impossible
# numbers with a call to mpair_cols (in practice you would also have mpair_rows and mpair_boxes).
print("\nNow testing mpair_cols to demo how this function could be critical to solving a puzzle.")
print("In this example test we are only testing col 4 - in practice we would test all columns.")
mpair_cols(4)
print("This call results in finding a matched pair of [3,7] which means 3's and 7's should be ")
print("removed from any non-matched-pair cells - they are 'impossible numbers'.\n")
print_pv_board()
print("Note that r9c4 used to have [3,8] but now shows only 8. This revealed single could now be posted in t_posted.")
print("Depending on how you wanted your game to flow, you could let the user do this or you could write code or")
print("alter a function to remove any discovered single automatically.\n")
# test running mpair_cols on all columns
print("Now lets reset the possible values and run mpair_cols on all the columns")
board_pv()
for col in range(1,10):
print("for col #"+str(col)+ " numbers to remove are: "+ str(mpair_cols(col)))
print()
print_pv_board()
print("\nSpecial Reminder - none of these impossible number removals are permanent! If we wanted to lock in a single")
print("that is revealed by impossible numbers removed we would create code to compare the output of print_pv_board with")
print("the data output after board_pv is called (which would return us to our starting state) and post any 'new' singles.")