Blog
Solucionador de Sudokus en Python
- Publicado por: Rafael Fernandez
- Categoría: Blog
Sudoku es un juego de lógica japonés en donde el objetivo es rellenar una celda de 9×9 casillas con números del 1 al 9, de manera que cada columna, fila y cada uno de las subceldas 3×3 que componen la celda 9×9 contengan cada número solo una vez. Frente al obvio reto matemático y lógico que este juego representa, no es de extrañar que se hayan desarrollado programas que ayuden a resolver sudokus a traves de algoritmos. En este artículo estaremos desarrollando un solucionador de Sudokus en Python.
En el siguiente script se desarrolla un solcionador de Sudokus escrito en Python 3, se definen 8 funciones, contando la función main() en la cual se ejecuta el código principal del script:
from random import choice def number_in_row(grid, row, number): """ Chequear si un número se encuentra en la fila especificada. """ return number in grid[row] def number_in_col(grid, col, number): """ Chequear si un número se encuentra en la columna especificada. """ return number in (row[col] for row in grid) def number_in_box(grid, row, col, number): """ Chequear si un número se encuentra en la caja a la que corresponde la posición especificada. """ # Obtener la caja a la que pertenece el número. box_row, box_col = box_by_pos(row, col) # Construir una lista con los números en la caja. numbers_in_box = unpack( row[box_col*3:box_col*3 + 3] for row in grid[box_row*3:box_row*3 + 3] ) return number in numbers_in_box def reduce(n): """ Reducir la posición 9x9 a 3x3. """ n /= 3 if n == 0 or n != int(n): n += 1 return int(n) def box_by_pos(row, col): # Trabajar temporalmente con base 1. row += 1 col += 1 # Obtener base 0 nuevamente. return reduce(row) - 1, reduce(col) - 1 def unpack(iterable): """ >>> list(unpack([[1, 2], [3, 4]])) [1, 2, 3, 4] """ for item in iterable: yield from item def get_possible_numbers(grid, row, col): """ Retorna números posibles para una determinada posición. """ for number in range(1, 10): if (not number_in_row(grid, row, number) and not number_in_col(grid, col, number) and not number_in_box(grid, row, col, number)): yield number def main(): while True: # Los ceros representan casilleros vacíos. grid = [ [3, 0, 5, 6, 2, 9, 0, 0, 7], [7, 0, 6, 1, 0, 8, 0, 0, 0], [8, 0, 1, 0, 0, 0, 2, 6, 5], [0, 0, 3, 0, 0, 5, 0, 7, 0], [6, 8, 7, 0, 0, 0, 0, 0, 0], [2, 0, 0, 7, 0, 0, 6, 0, 0], [4, 7, 9, 5, 8, 0, 0, 2, 0], [1, 0, 0, 4, 3, 0, 5, 0, 9], [0, 0, 8, 9, 0, 0, 0, 0, 6], ] s = \ """\ +-----------------------+ | {} {} {} | {} {} {} | {} {} {} | | {} {} {} | {} {} {} | {} {} {} | | {} {} {} | {} {} {} | {} {} {} | +-----------------------+ | {} {} {} | {} {} {} | {} {} {} | | {} {} {} | {} {} {} | {} {} {} | | {} {} {} | {} {} {} | {} {} {} | +-----------------------+ | {} {} {} | {} {} {} | {} {} {} | | {} {} {} | {} {} {} | {} {} {} | | {} {} {} | {} {} {} | {} {} {} | +-----------------------+ """ while True: possible_numbers = { (row, col): None for row in range(9) for col in range(9) } # Obtener una lista de números posibles para cada una de # las posiciones vacías. for row in range(9): for col in range(9): number = grid[row][col] if number == 0: options = list( get_possible_numbers(grid, row, col) ) if options: possible_numbers[(row, col)] = options # Remover valores vacíos y ordenar por la cantidad de # posibilidades. possible_numbers = sorted( ( (k, v) for (k, v) in possible_numbers.items() if v is not None ), key=lambda kv: len(kv[1]) ) if possible_numbers: # Obtener el primer item. (row, col), numbers = possible_numbers[0] # Fuerza bruta: obtener un número aleatorio de la # lista de posibiilidades hasta que la grilla esté # completa. grid[row][col] = choice(numbers) else: break # Chequear si la fuerza bruta dió resultado: si no hay más # ceros en la grilla entonces el Sudoku está resuelto. if 0 not in unpack(grid): print(s.format(*(unpack(grid)))) break if __name__ == "__main__": main()
Explicación de solucionador de Sudoku
Bien, hagamos un análisis detallado de lo que realiza cada función y de como se ejecuta el script solucionador de sudoku.
- main() : en esta función se ejecuta el proceso principal del script, se ejecuta un ciclo while en el que se crea la celda de 9×9 que se quiere resolver. Dentro de este ciclo while se declara otro ciclo while en el que se itera a traves de cada casilla de la celda 9×9 y se estiman los posibles números que pueden aparecer en cada casilla. Luego se remueven los valores vacíos y se ordenan por la cantidad de valores posibles. Luego se itera de nuevo cada casilla y a traves de la fuerza bruta obtenemos un número aleatorio de la lista de valores posibles hasta que la celda este completa.
- number_in_row(grid, row, number) : esta función verifica que el número que le damos como argumento se encuentra en la fila especificada
- number_in_col(grid, col, number) : esta función verifica si el número que le damos como argumento se encuentra en la columna especificada
- number_in_box(grid, row, col, number) : aquí verificamos si el número indicado se encuentra en la subcelda 3×3 que corresponde a la posición especificada.
- reduce(n): pasamos de una posición en la celda 9×9 a una posición en una subcelda 3×3.
- box_by_pos(row, col) : función de utilidad que ayuda a number_in_box() a verificar si un número se encuentra en la subcelda de 3×3.
- get_possible_numbers(grid, row, col) : retorna los valores posibles para una casilla determinada.
Ejecutando solucionador de sudokus
Pasando la siguiente casilla:
grid = [ [3, 0, 5, 6, 2, 9, 0, 0, 7], [7, 0, 6, 1, 0, 8, 0, 0, 0], [8, 0, 1, 0, 0, 0, 2, 6, 5], [0, 0, 3, 0, 0, 5, 0, 7, 0], [6, 8, 7, 0, 0, 0, 0, 0, 0], [2, 0, 0, 7, 0, 0, 6, 0, 0], [4, 7, 9, 5, 8, 0, 0, 2, 0], [1, 0, 0, 4, 3, 0, 5, 0, 9], [0, 0, 8, 9, 0, 0, 0, 0, 6], ]
Nuestro script soluciona existosamente el sudoku:
+-----------------------+ | 3 4 5 | 6 2 9 | 8 1 7 | | 7 2 6 | 1 5 8 | 3 9 4 | | 8 9 1 | 3 7 4 | 2 6 5 | +-----------------------+ | 9 1 3 | 8 6 5 | 4 7 2 | | 6 8 7 | 2 4 3 | 9 5 1 | | 2 5 4 | 7 9 1 | 6 3 8 | +-----------------------+ | 4 7 9 | 5 8 6 | 1 2 3 | | 1 6 2 | 4 3 7 | 5 8 9 | | 5 3 8 | 9 1 2 | 7 4 6 | +-----------------------+