# 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.")