Mientras escribía el crucigrama japonés en 4 horas

Miro perezosamente la lista de cursos impartidos por escolares recientemente presentados por colegas de Sirius ... Entonces, ¿qué es esto? ¿"Buscar objetos combinatorios utilizando solucionadores SAT"? "¿Hicimos un solucionador de sudoku, crucigramas japoneses y más?"

Aparece el pensamiento de que los exhaustivos problemas NP son reducibles entre sí y, en particular, son reducibles para la búsqueda de la viabilidad de una fórmula booleana . Uno de los autores de Habr expresó esta idea aquí , y francamente, para mí es como magia.

Por supuesto, como una persona que ha completado un curso de matemática discreta y la complejidad de los algoritmos, teóricamente sé que las tareas son reducibles entre sí. Pero generalmente es difícil hacer esto, y prefiero tomar mi palabra para los profesores y otras personas inteligentes.

Pero luego se ofrece a ... ¡NIÑOS DE ESCUELA! En el interior, el punzón comenzó a generar p ... creatividad y declaró: "Bueno, probablemente no sea difícil sujetarlo, una vez que se les ofrezca a los estudiantes. ¿No puedo resolverlo? Miren, prometen que usarán la biblioteca Python, pero generalmente conozco la pitón ... "

Y era alrededor de las 9 pm, lo que de alguna manera opacó mi mirada crítica sobre la complejidad del problema ... (en realidad, más crónicas de programación de 4 horas)

21:02 La primera etapa de la búsqueda: estoy tratando de instalar la biblioteca en Python. Por experiencia con redes neuronales, ya sé que, de hecho, el código a veces lleva menos tiempo que la configuración de paquetes.

Al usar la instalación clásica de pip, pycosat no está instalado, produce un error en el espíritu de Microsoft Visual C ++ 14.0 .

Subimos para buscar la fuente del problema, llegamos a Stackoverflow , donde encontramos un pequeño enlace a las herramientas de compilación de Visual C ++ 2015 requeridas , que aparentemente incluyen el compilador de C ++.

Descargar, tratando de instalar ... wow, ¡requiere 4 GB! En realidad solo necesito un compilador, pero bueno.

Mientras esto se descarga e instala, seguimos los enlaces en el programa publicado desde Sirius ... Sí, no mucho: ejemplos de proyectos de Python y una lista de posibles tareas para el curso. Sin presentaciones, sin videos para comprender rápidamente cómo trabajar con la biblioteca.

Bien, comencemos analizando los ejemplos. Vamos a queens_pycosat.py ... Wow, en el camino esta es la solución al problema familiar de colocar reinas en un tablero de ajedrez (la tarea es organizar 8 reinas para que no se golpeen entre sí).

21: 10-21: 30 Entendemos y tratamos de comenzar.

La lógica general comienza a rastrearse:
SAT- () (1, 2, 3),(-1, 2 ..), (True/False).

, , .

( , ); , , 8x8 = 64 , .

(1 2 3… 8) — 1 . — [1,2,3...8]
— (9… 16) ..

— 1 !
[ 1 2] ( — ). [-1,-2],[-1,-3] .

, , . , .

( ) — (1 2… 8) ( 1 2) (...). , — .

Sí, no era tan inteligente de inmediato, ahora que lo he descubierto ...

En general, copiamos el código de enseñanza para nosotros mismos. Para asegurarse de que entendemos algo, desactive la comprobación de diagonales, luego la tarea se convierte en una tarea sobre torres.

Comenzamos, recibimos: ¡Funciona! 21:35 Habiendo familiarizado un poco con la documentación de la biblioteca, descubrimos que existen desafíos para encontrar una solución o todas las soluciones. Dime, mi querido amigo, ¿cuántos arreglos de torres hay en un tablero de 8x8? ¿Encontrarás todo? La máquina pensó por un tiempo, y comencé a buscar en Google la tabla factorial. Un minuto después, el solucionador respondió: "Se encontraron 40320 soluciones". Comprueba - ¡realmente 8 !, todo está correcto.

| | | | | | | |x|
| | | | | | |x| |
| | | | | |x| | |
| | | | |x| | | |
| | | |x| | | | |
| | |x| | | | | |
| |x| | | | | | |
|x| | | | | | | |











Eliminé la condición de que las torres no se golpeen verticalmente ... pero ¿cuánto será ahora? Debe ser 8 ^ 8 = 16777216. Entonces caí en la cuenta de que tardaría mucho en contar; interrumpimos el proceso.

También encontré una forma rápida de obtener 4 soluciones sin calcular todo, desde todo en la misma biblioteca. Esto se hace: encontramos una solución, después de lo cual la agregamos a las excepciones de la fórmula booleana ...

Cómo deducir varias soluciones en python
for cnter in range(4):
  sol = pycosat.solve(clauses)
  if isinstance(sol, list):
    print(sol)
    clauses.append([-x for x in sol]) #   -      
  else: #    - 
    return
#     ,    ,   .


En general, obtenemos algunas soluciones más para el "problema de la torre": 21:45 E: En general, todo está claro, en todo caso, ahora puedo solucionarlo. Voy a leer un libro y un spa ... INICIO CREATIVO: Bueno, entiendes cómo funciona todo. ¡Y escribamos un pequeño crucigrama simple de crucigramas japoneses! Tome el caso más simple: en cada fila y columna no habrá más de un dígito (y, en consecuencia, un bloque). Simplemente busca entre las posibles opciones para la fila / columna, aquí es rápido, y luego lo carga en el solucionador. Hagámoslo, ¿eh? Yo: ( sin sentir una emboscada ) Bueno, vamos ... parece que la verdad no es larga ... Por simplicidad, solo tomamos crucigramas cuadrados NxN; al mismo tiempo, puede tener listo el código de salida y pensar dónde no son necesarios los horizontales y verticales ...

| | | | | | | |x|
| | | | | | |x| |
| | | | | |x| | |
| | | | |x| | | |
| | | |x| | | | |
| |x| | | | | | |
| | |x| | | | | |
|x| | | | | | | |











22:10 No funciona: ((
Intentamos con el crucigrama 3x3, en el formato 1-0-1 (hasta ahora solo los bloques son horizontales, no tenemos en cuenta las verticales). Muestra todas las celdas vacías, la infección, ni siquiera tiene en cuenta los bloques llenos ...

Bien, en detalle Profundizamos en el formato de transferencia de datos al solucionador, y luego algo, por decirlo suavemente, se revela inesperado ...

, «-» ?

/ /, — . , .

[1] 3 , :
[X__],[_X_],[__X]
, ,
[1,-2,-3],[-1,2,-3],[-1,-2,3]

, …
:
[1 -2 -3] [-1, 2...] [...]

, . , . — , !

Emboscada. En realidad, hay dos opciones: elaborar las condiciones para un crucigrama en KNF o idear cómo convertir DNF a KNF (bueno, alguien debería haber pensado en esto, ¡especialmente porque ambas son fórmulas booleanas!)

22:50 Terminé la merienda. No pensé en cómo crear condiciones para KNF (más tarde descubrí que estaban descritas en el artículo , pero fue mucho tiempo programarlas y, francamente, antideportivas).

Entonces estamos buscando un convertidor de DNF a CNF. Las descripciones formales de las transformaciones en el estilo académico están dispersas en Internet ... ¿por qué las necesito? Definitivamente no voy a quedarme con ellos ahora, resolvería el problema. Entonces, vemos la única (!) Biblioteca de Python con el nombre y las funciones apropiadas. Ahora nos conectaremos ...

23:10Funciona de manera divertida: crea variables adicionales y, a través de ellas, trae DNF a CNF. Aparentemente, honestamente hacer KNF es bastante difícil ... ¡

Entonces, tampoco funciona! ¿Qué pasa?
Enviamos la entrada de prueba de DNF: lstall = [[1,2,3]]. Es decir, esperamos que solo [1,2,3] (corresponde a la línea completa [XXX]) sea una solución válida. La

biblioteca proporciona KNF: cnf = [[-1, -2, -3, 4], [1 , -4], [2, -4], [3, -4]]
No está claro, pero es muy interesante. Bueno, digamos que te creo ...

Le pedimos a pycosat que encuentre todas las soluciones para cnf:
[1, 2, 3, 4]
[1, 2, -3, -4]
[1, -2, -3, -4]
[1 , -2, 3, -4]
[-1, -2, -3, -4]
[-1, -2, 3, -4]
[-1, 2, -3, -4]
[-1, 2, 3, -4] ¡

En lugar del esperado, hay ocho de ellos! ¿Qué hacer?

, , 4 True. ?

: cnf2 = [[-1, -2, -3, 4], [1, -4], [2, -4], [3, -4], [4]]
pycosat cnf2:
1: [1, 2, 3, 4]

, , ! , , .
— !

23:10 Ella decide! Aquí hay un crucigrama 1-3-1 vertical y horizontalmente: Aquí hay una solución doble de un crucigrama 1-0-1 vertical y horizontalmente: Yo: Entonces, todo funciona para mí, ya terminé . Wow, ya son las 11 p.m. Spa ... INICIO CREATIVO: ¿Entiendes que te queda muy poco para resolver los crucigramas japoneses? Solo es necesario escribir un procedimiento recursivo que brinde todas las opciones de línea, no para un bloque, sino para N ... Yo: Bueno ... parece fácil, sí ... 00:00 A medianoche, el tupito de la cabeza ya no cocina muy bien, pero continúo depurando recursividad Términos del crucigrama japonés: entre los bloques debe haber al menos un espacio, pero puede haber más de uno.

| |x| |
|x|x|x|
| |x| |




1
| | |x|
| | | |
|x| | |
2
|x| | |
| | | |
| | |x|












Entonces, bajo la condición [1,2,1] para 6 celdas, el programa debe generar la única opción
[X_XX_X],
y para 7 celdas ya hay 4 opciones:
[X_XX_X_],
[X_XX__X],
[X__XX_X],
[_X_XX_X],

todo el tiempo en Estoy calculando por error la ubicación de las celdas por una, y al diablo con eso, es más fácil comenzar y arreglar que pensar ...

00:30 ¿ Funciona ? ¿Qué más revisar? Entonces, dónde obtener una descripción de crucigramas orientada a la máquina ... En este sitio, no, en esto ... no ... De acuerdo, al diablo interrumpiré los bolígrafos. 00:50 Realmente funciona O_O Aquí hay un cangrejo con jcrosswords.ru/crossword/65

3x3
rows = [[1,1],[0],[1,1]]
cols = [[1,1],[0],[1,1]]

:
|X| |X|
| | | |
|X| |X|

4x4
rows = [[1,1],[0],[0],[1,1]]
cols = [[1],[1],[1],[1]]

:
1
| |X| |X|
| | | | |
| | | | |
|X| |X| |
2
|X| |X| |
| | | | |
| | | | |
| |X| |X|







decidido por mi solucionador. El tamaño máximo probado es 10x10, ahora soy muy vago para interrumpir más crucigramas en el programa ... - Yo: ¡Vaya, ya es la una de la mañana! Espera, ¿me tomó solo 4 horas desde el momento en que no sabía nada sobre el solucionador de SAT? Entonces, ¿cómo fue en absoluto? (se escriben notas rápidas sobre lo que sucedió ...)

|x|_|x|_|_|_|_|x|_|x|
|x|x|x|_|_|_|_|x|x|x|
|_|x|_|_|_|_|_|_|x|_|
|_|x|_|x|_|_|x|_|x|_|
|_|x|x|x|x|x|x|x|x|_|
|_|_|x|x|x|x|x|x|_|_|
|x|x|x|x|x|x|x|x|x|x|
|_|_|x|x|x|x|x|x|_|_|
|x|x|_|x|x|x|x|_|x|x|
|x|_|_|_|_|_|_|_|_|x|








Epílogo.

Por la mañana . En Habr se encuentra el artículo sobre un solucionador sobre Rust . La descarga de crucigramas no funciona, pero después de ir al artículo de Github especificado en el artículo, encontramos un enlace oculto para exportar crucigramas en un formato legible por máquina en el sitio web de crucigramas Webpbn . La opción "Formato para la versión 1 del solucionador de Python de Kyle Keen" es adecuada para nosotros, es la misma que la nuestra. Cuando no necesita interrumpir crucigramas con bolígrafos, las cosas van más rápido. Rápidamente se hace evidente que el máximo que un solucionador puede resolver son crucigramas en la región de 30x30. El problema principal es la generación misma de variantes de líneas individuales, en el caso de un montón de bloques de estas condiciones, MUY mucho y todo comienza a disminuir:



1. En primer lugar, las opciones de cadena tardan mucho tiempo en generarse. Generar solo todas las opciones para la cadena [2,1,3,1,4] de longitud 60 ya lleva aproximadamente medio minuto ... (hay 2118760 de ellas, si alguien está interesado)
2. Además, la conversión DNF-> CNF crea demasiadas condiciones y variables adicionales ...

Pero Si hay pocos bloques, incluso los crucigramas grandes se pueden resolver en un tiempo significativo.

Gato - todo está bien
20x20
51511 clauses...
2851 last var number
--- 0.05186057090759277 seconds for clauses gen ---
1
| | |X|X| | | | | | | | | | | | | | | | |
| |X|X| | | | | | | | | | | | | | | | | |
| |X| | | | | | | | | | | | | | | | | | |
| |X| | | | | | | | | | | | | | | | | | |
| |X| | | | | |X|X|X| | | | | | | | | | |
|X|X| | | | |X|X|X|X|X| | | | | | | | | |
|X| | | | |X|X|X|X|X|X|X| | | |X| | | |X|
|X| | | | |X|X|X|X|X|X|X|X| | |X|X| |X|X|
|X| | | |X|X|X|X|X|X|X|X|X| | |X|X|X|X|X|
|X|X| | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|
| |X| |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|
| |X|X|X|X|X|X|X| |X|X|X|X|X|X|X|X|X|X|X|
| | |X|X|X|X|X| | | |X|X|X|X|X| |X|X|X| |
| | |X|X|X|X|X| | | |X|X|X|X| | | | | | |
| | | |X|X|X| | | | | |X|X|X| | | | | | |
| | | |X|X| | | | | | |X|X| | | | | | | |
| | |X|X| | | | | | | | |X| | | | | | | |
| | |X| | | | | | | | | |X| | | | | | | |
| | |X|X| | | | | | | | |X|X| | | | | | |
| | |X|X| | | | | | | | |X|X| | | | | | |
--- 0.02073383331298828 seconds for solve ---


En un deseo de probar más crucigramas, rápidamente hizo la alineación: cualquier crucigrama se reduce a un cuadrado cuando llenamos las líneas restantes con ceros. Sí, no es óptimo, pero ahora soy reacio a rehacer el código ...

Caballo - 30x38
22
13622799 clauses... -
350737 last var number
--- 14.18206787109375 seconds for clauses gen ---
1
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X| | | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X| | | | |
| | | | | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X| | | |
| | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X|X| |X|X| | |
| | | | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X|X|X|X|X| |
| | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|
| | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X|X| |X|X|X|X|X|
| | | | | | | | | | | | | | | | | | | | | | | | |X|X|X|X|X|X|X| | | | | |X| |
| | | | | | | | | | |X|X|X|X| | | | | | | | | |X|X|X|X|X|X|X|X| | | | | | | |
| | | | | | | | |X|X|X|X|X|X|X|X|X| | | |X|X|X|X|X|X|X|X|X|X| | | | | | | | |
| | | | | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | | | | | |
| | | | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | | | | | | |
| | | | |X|X|X| |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | | | | | | |
| | | |X|X|X| | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | | | | | | |
| | |X|X|X| | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | | |
| |X|X|X| | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | |
|X|X|X|X| | | |X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X|X| | | |X|X| | | |
| |X|X| | | |X|X|X|X|X| | | | |X|X|X|X|X|X|X|X|X|X|X|X| | | | | | |X|X| | | |
| | |X| | | |X|X|X|X| |X| | | | | | |X|X|X|X|X|X| | | |X| | | | | |X|X| | | |
| | | | | |X|X|X|X| |X|X| | | | | | | | | | | | | |X|X|X| | | | | |X|X| | | |
| | | | |X|X|X|X| |X|X| | | | | | | | | | | | | | |X|X|X| | | | | |X|X| | | |
| | | | |X|X|X| | |X|X| | | | | | | | | | | | | | | |X|X| | | | | |X|X| | | |
| | | | |X|X| | | |X|X| | | | | | | | | | | | | | | |X|X| | | | |X|X|X| | | |
| | | |X|X|X| | | | |X|X| | | | | | | | | | | | | | |X|X| | | | |X|X| | | | |
| | | |X|X| | | | | |X|X| | | | | | | | | | | | | | |X|X| | | | |X| | | | | |
| | | |X|X| | | | | |X|X| | | | | | | | | | | | | | |X|X| | | | | | | | | | |
| | | |X|X| | | | | | |X|X| | | | | | | | | | | | | |X|X| | | | | | | | | | |
| | | |X|X| | | | | | |X|X| | | | | | | | | | | | | |X|X| | | | | | | | | | |
| | | | |X|X| | | | | | |X|X| | | | | | | | | | | | | |X|X| | | | | | | | | |
| | | | |X|X|X| | | | | |X|X|X| | | | | | | | | | | | |X|X|X| | | | | | | | |

--- 17.535892963409424 seconds for solve ---



En la publicación original sobre el solucionador SAT , las condiciones y las variables se generan con mayor honestidad; por lo tanto, la solución es más rápida y probablemente requiere menos memoria. Cuando decido un caballo, me lleva alrededor de 2.7 GB de RAM, y ocupa algunas cosas complicadas para los 12 instalados, y luego comienzan a cambiar frenéticamente al disco ...

Más de las conclusiones:

1. Me di cuenta de por qué los ejemplos de Pycosat más buscados en Google son soluciones Sudoku. Las reglas de sudoku son mucho más fáciles de poner en el solucionador, ya que no es necesario describir la compleja relación entre bloques y celdas, básicamente solo las condiciones "cada dígito en cada fila" y "solo un dígito en una fila".
Pero me encanta el sudoku mucho menos, así que no me arrepiento de la decisión que he elegido.

2. Me gustó cómo puedes mezclar DNF y CNF. Si no quieres escribir las condiciones correctas, no lo hagas; solo genera todas las opciones. Por supuesto, esto no es óptimo, pero funciona.

Por supuesto, no establecí récords para la velocidad de resolución de crucigramas, pero la pasé bien. ¡Lo más importante es que puedo hacerlo! :) ¡Lo que también te deseo!

All Articles