Search code examples
pythoncycle

For cycle gets stuck in Python


My code below is getting stuck on a random point:

import functions
from itertools import product
from random import randrange

values = {}
tables = {}
letters = "abcdefghi"
nums = "123456789"
for x in product(letters, nums): #unnecessary
    values[x[0] + x[1]] = 0
for x in product(nums, letters): #unnecessary
    tables[x[0] + x[1]] = 0

for line_cnt in range(1,10):
    for column_cnt in range(1,10):
        num = randrange(1,10)
        table_cnt = functions.which_table(line_cnt, column_cnt) #Returns a number identifying the table considered
        #gets the values already in the line and column and table considered
        line = [y for x,y in values.items() if x.startswith(letters[line_cnt-1])]
        column = [y for x,y in values.items() if x.endswith(nums[column_cnt-1])]
        table = [x for x,y in tables.items() if x.startswith(str(table_cnt))]
        #if num is not contained in any of these then it's acceptable, otherwise find another number 
        while num in line or num in column or num in table:
            num = randrange(1,10)
        values[letters[line_cnt-1] + nums[column_cnt-1]] = num #Assign the number to the values dictionary
        print(line_cnt) #debug
print(sorted(values)) #debug

As you can see it's a program that generates random sudoku schemes using 2 dictionaries : values that contains the complete scheme and tables that contains the values for each table.

Example :

5th square on the first line = 3
    |
    v
values["a5"] = 3
tables["2b"] = 3

So what is the problem? Am I missing something?


Solution

  • import functions
    ...
            table_cnt = functions.which_table(line_cnt, column_cnt) #Returns a number identifying the table considered
    

    It's nice when we can execute the code right ahead on our own computer to test it. In other words, it would have been nice to replace "table_cnt" with a fixed value for the example (here, a simple string would have sufficed).

    for x in product(letters, nums):
        values[x[0] + x[1]] = 0
    

    Not that important, but this is more elegant:

    values = {x+y: 0 for x, y in product(letters, nums)}
    

    And now, the core of the problem:

    while num in line or num in column or num in table:
        num = randrange(1,10)
    

    This is where you loop forever. So, you are trying to generate a random sudoku. From your code, this is how you would generate a random list:

    nums = []
    for _ in range(9):
        num = randrange(1, 10)
        while num in nums:
            num = randrange(1, 10)
        nums.append(num)
    

    The problem with this approach is that you have no idea how long the program will take to finish. It could take one second, or one year (although, that is unlikely). This is because there is no guarantee the program will not keep picking a number already taken, over and over.

    Still, in practice it should still take a relatively short time to finish (this approach is not efficient but the list is very short). However, in the case of the sudoku, you can end up in an impossible setting. For example:

    line = [6, 9, 1, 2, 3, 4, 5, 8, 0]
    column = [0, 0, 0, 0, 7, 0, 0, 0, 0]
    

    Where those are the first line (or any line actually) and the last column. When the algorithm will try to find a value for line[8], it will always fail since 7 is blocked by column.

    If you want to keep it this way (aka brute force), you should detect such a situation and start over. Again, this is very unefficient and you should look at how to generate sudokus properly (my naive approach would be to start with a solved one and swap lines and columns randomly but I know this is not a good way).