Funciton , no competitivo
¡ACTUALIZAR! ¡Mejora masiva del rendimiento! ¡n = 7 ahora se completa en menos de 10 minutos! Ver explicación en la parte inferior!
Fue muy divertido escribirlo. Este es un solucionador de fuerza bruta para este problema escrito en Funciton. Algunos factoides:
- Acepta un número entero en STDIN. Cualquier espacio en blanco extraño lo rompe, incluida una nueva línea después del entero.
- Utiliza los números 0 a n - 1 (no 1 a n ).
- Llena la cuadrícula "hacia atrás", por lo que obtienes una solución donde se lee la fila inferior en
3 2 1 0
lugar de donde se lee la fila superior 0 1 2 3
.
- Produce correctamente
0
(la única solución) para n = 1.
- Salida vacía para n = 2 yn = 3.
- Cuando se compila en un exe, toma aproximadamente 8¼ minutos para n = 7 (fue aproximadamente una hora antes de la mejora del rendimiento). Sin compilar (usando el intérprete) toma aproximadamente 1,5 veces más tiempo, por lo que vale la pena usar el compilador.
- Como un hito personal, esta es la primera vez que escribí un programa Funciton completo sin primero escribir el programa en un lenguaje de pseudocódigo. Aunque lo escribí en C # real primero.
- (Sin embargo, esta no es la primera vez que hago un cambio para mejorar masivamente el rendimiento de algo en Funciton. La primera vez que lo hice fue en la función factorial. Cambiar el orden de los operandos de la multiplicación hizo una gran diferencia debido a cómo funciona el algoritmo de multiplicación . En caso de que tuviera curiosidad.)
Sin más preámbulos:
┌────────────────────────────────────┐ ┌─────────────────┐
│ ┌─┴─╖ ╓───╖ ┌─┴─╖ ┌──────┐ │
│ ┌─────────────┤ · ╟─╢ Ӂ ╟─┤ · ╟───┤ │ │
│ │ ╘═╤═╝ ╙─┬─╜ ╘═╤═╝ ┌─┴─╖ │ │
│ │ └─────┴─────┘ │ ♯ ║ │ │
│ ┌─┴─╖ ╘═╤═╝ │ │
│ ┌────────────┤ · ╟───────────────────────────────┴───┐ │ │
┌─┴─╖ ┌─┴─╖ ┌────╖ ╘═╤═╝ ┌──────────┐ ┌────────┐ ┌─┴─╖│ │
│ ♭ ║ │ × ╟───┤ >> ╟───┴───┘ ┌─┴─╖ │ ┌────╖ └─┤ · ╟┴┐ │
╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌─────┤ · ╟───────┴─┤ << ╟─┐ ╘═╤═╝ │ │
┌───────┴─────┘ ┌────╖ │ │ ╘═╤═╝ ╘══╤═╝ │ │ │ │
│ ┌─────────┤ >> ╟─┘ │ └───────┐ │ │ │ │ │
│ │ ╘══╤═╝ ┌─┴─╖ ╔═══╗ ┌─┴─╖ ┌┐ │ │ ┌─┴─╖ │ │
│ │ ┌┴┐ ┌───────┤ ♫ ║ ┌─╢ 0 ║ ┌─┤ · ╟─┤├─┤ ├─┤ Ӝ ║ │ │
│ │ ╔═══╗ └┬┘ │ ╘═╤═╝ │ ╚═╤═╝ │ ╘═╤═╝ └┘ │ │ ╘═╤═╝ │ │
│ │ ║ 1 ╟───┬┘ ┌─┴─╖ └───┘ ┌─┴─╖ │ │ │ │ │ ┌─┴─╖ │
│ │ ╚═══╝ ┌─┴─╖ │ ɓ ╟─────────────┤ ? ╟─┘ │ ┌─┴─╖ │ ├─┤ · ╟─┴─┐
│ ├─────────┤ · ╟─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴────┤ + ╟─┘ │ ╘═╤═╝ │
┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═╧═╕ ╔═══╗ ┌───╖ ┌─┴─╖ ┌─┴─╖ ╘═══╝ │ │ │
┌─┤ · ╟─┤ · ╟───┐ └┐ └─╢ ├─╢ 0 ╟─┤ ⌑ ╟─┤ ? ╟─┤ · ╟──────────────┘ │ │
│ ╘═╤═╝ ╘═╤═╝ └───┐ ┌┴┐ ╚═╤═╛ ╚═╤═╝ ╘═══╝ ╘═╤═╝ ╘═╤═╝ │ │
│ │ ┌─┴─╖ ┌───╖ │ └┬┘ ┌─┴─╖ ┌─┘ │ │ │ │
│ ┌─┴───┤ · ╟─┤ Җ ╟─┘ └────┤ ? ╟─┴─┐ ┌─────────────┘ │ │
│ │ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │ │╔════╗╔════╗ │ │
│ │ │ ┌──┴─╖ ┌┐ ┌┐ ┌─┴─╖ ┌─┴─╖ │║ 10 ║║ 32 ║ ┌─────────────────┘ │
│ │ │ │ << ╟─┤├─┬─┤├─┤ · ╟─┤ · ╟─┘╚══╤═╝╚╤═══╝ ┌──┴──┐ │
│ │ │ ╘══╤═╝ └┘ │ └┘ ╘═╤═╝ ╘═╤═╝ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ │
│ │ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ┌─┴─╖ ╔═╧═╕ └─┤ ? ╟─┤ · ╟─┤ % ║ │
│ └─────┤ · ╟─┤ · ╟──┤ Ӂ ╟──┤ ɱ ╟─╢ ├───┐ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ │
│ ╘═╤═╝ ╘═╤═╝ ╘═╤═╝ ╘═══╝ ╚═╤═╛ ┌─┴─╖ ┌─┴─╖ │ └────────────────────┘
│ └─────┤ │ └───┤ ‼ ╟─┤ ‼ ║ │ ┌──────┐
│ │ │ ╘═══╝ ╘═╤═╝ │ │ ┌────┴────╖
│ │ │ ┌─┴─╖ │ │ │ str→int ║
│ │ └──────────────────────┤ · ╟───┴─┐ │ ╘════╤════╝
│ │ ┌─────────╖ ╘═╤═╝ │ ╔═╧═╗ ┌──┴──┐
│ └──────────┤ int→str ╟──────────┘ │ ║ ║ │ ┌───┴───┐
│ ╘═════════╝ │ ╚═══╝ │ │ ┌───╖ │
└───────────────────────────────────────────────────────┘ │ └─┤ × ╟─┘
┌──────────────┐ ╔═══╗ │ ╘═╤═╝
╔════╗ │ ╓───╖ ┌───╖ │ ┌───╢ 0 ║ │ ┌─┴─╖ ╔═══╗
║ −1 ║ └─╢ Ӝ ╟─┤ × ╟──┴──────┐ │ ╚═╤═╝ └───┤ Ӂ ╟─╢ 0 ║
╚═╤══╝ ╙───╜ ╘═╤═╝ │ │ ┌─┴─╖ ╘═╤═╝ ╚═══╝
┌─┴──╖ ┌┐ ┌───╖ ┌┐ ┌─┴──╖ ╔════╗ │ │ ┌─┤ ╟───────┴───────┐
│ << ╟─┤├─┤ ÷ ╟─┤├─┤ << ║ ║ −1 ║ │ │ │ └─┬─╜ ┌─┐ ┌─────┐ │
╘═╤══╝ └┘ ╘═╤═╝ └┘ ╘═╤══╝ ╚═╤══╝ │ │ │ └───┴─┘ │ ┌─┴─╖ │
│ └─┘ └──────┘ │ │ └───────────┘ ┌─┤ ? ╟─┘
└──────────────────────────────┘ ╓───╖ └───────────────┘ ╘═╤═╝
┌───────────╢ Җ ╟────────────┐ │
┌────────────────────────┴───┐ ╙───╜ │
│ ┌─┴────────────────────┐ ┌─┴─╖
┌─┴─╖ ┌─┴─╖ ┌─┴─┤ · ╟──────────────────┐
│ ♯ ║ ┌────────────────────┤ · ╟───────┐ │ ╘═╤═╝ │
╘═╤═╝ │ ╘═╤═╝ │ │ │ ┌───╖ │
┌─────┴───┘ ┌─────────────────┴─┐ ┌───┴───┐ ┌─┴─╖ ┌─┴─╖ ┌─┤ × ╟─┴─┐
│ │ ┌─┴─╖ │ ┌───┴────┤ · ╟─┤ · ╟──────────┤ ╘═╤═╝ │
│ │ ┌───╖ ┌───╖ ┌──┤ · ╟─┘ ┌─┴─┐ ╘═╤═╝ ╘═╤═╝ ┌─┴─╖ │ │
│ ┌────┴─┤ ♭ ╟─┤ × ╟──┘ ╘═╤═╝ │ ┌─┴─╖ ┌───╖└┐ ┌──┴─╖ ┌─┤ · ╟─┘ │
│ │ ╘═══╝ ╘═╤═╝ ┌───╖ │ │ │ × ╟─┤ Ӝ ╟─┴─┤ ÷% ╟─┐ │ ╘═╤═╝ ┌───╖ │
│ ┌─────┴───┐ ┌────┴───┤ Ӝ ╟─┴─┐ │ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ │ │ └───┤ Ӝ ╟─┘
│ ┌─┴─╖ ┌───╖ │ │ ┌────╖ ╘═╤═╝ │ └───┘ ┌─┴─╖ │ │ └────┐ ╘═╤═╝
│ │ × ╟─┤ Ӝ ╟─┘ └─┤ << ╟───┘ ┌─┴─╖ ┌───────┤ · ╟───┐ │ ┌─┴─╖ ┌───╖ │ │
│ ╘═╤═╝ ╘═╤═╝ ╘══╤═╝ ┌───┤ + ║ │ ╘═╤═╝ ├──┴─┤ · ╟─┤ × ╟─┘ │
└───┤ └────┐ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ │ ╔═══╗ ┌─┴─╖ ┌─┴─╖ ╘═╤═╝ ╘═╤═╝ │
┌─┴─╖ ┌────╖ │ ║ 0 ╟─┤ ? ╟─┤ = ║ ┌┴┐ │ ║ 0 ╟─┤ ? ╟─┤ = ║ │ │ ┌────╖ │
│ × ╟─┤ << ╟─┘ ╚═══╝ ╘═╤═╝ ╘═╤═╝ └┬┘ │ ╚═══╝ ╘═╤═╝ ╘═╤═╝ │ └─┤ << ╟─┘
╘═╤═╝ ╘═╤══╝ ┌┐ ┌┐ │ │ └───┘ ┌─┴─╖ ├──────┘ ╘═╤══╝
│ └────┤├──┬──┤├─┘ ├─────────────────┤ · ╟───┘ │
│ └┘┌─┴─╖└┘ │ ┌┐ ┌┐ ╘═╤═╝ ┌┐ ┌┐ │
└────────────┤ · ╟─────────┘ ┌─┤├─┬─┤├─┐ └───┤├─┬─┤├────────────┘
╘═╤═╝ │ └┘ │ └┘ │ └┘ │ └┘
└───────────────┘ │ └────────────┘
Explicación de la primera versión.
La primera versión tardó aproximadamente una hora en resolver n = 7. Lo siguiente explica principalmente cómo funcionaba esta versión lenta. En la parte inferior, explicaré qué cambio hice para que llegue a menos de 10 minutos.
Una excursión en pedazos
Este programa necesita bits. Necesita muchos bits, y los necesita en todos los lugares correctos. Los programadores experimentados de Funciton ya saben que si necesita n bits, puede usar la fórmula
que en Funciton se puede expresar como
Al hacer mi optimización de rendimiento, se me ocurrió que puedo calcular el mismo valor mucho más rápido con esta fórmula:
Espero que me perdones por no haber actualizado todos los gráficos de ecuaciones en esta publicación en consecuencia.
Ahora, digamos que no quieres un bloque contiguo de bits; de hecho, quieres n bits a intervalos regulares cada k -ésimo bit, así:
LSB
↓
00000010000001000000100000010000001
└──┬──┘
k
La fórmula para esto es bastante sencilla una vez que lo sabes:
En el código, la función Ӝ
toma los valores n y k y calcula esta fórmula.
Realizar un seguimiento de los números usados
Hay n ² números en la cuadrícula final, y cada número puede ser cualquiera de n valores posibles. Para realizar un seguimiento de qué números están permitidos en cada celda, mantenemos un número que consta de n ³ bits, en el que se establece un bit para indicar que se toma un valor particular. Inicialmente este número es 0, obviamente.
El algoritmo comienza en la esquina inferior derecha. Después de "adivinar" el primer número es un 0, debemos hacer un seguimiento del hecho de que el 0 ya no está permitido en ninguna celda a lo largo de la misma fila, columna y diagonal:
LSB (example n=5)
↓
10000 00000 00000 00000 10000
00000 10000 00000 00000 10000
00000 00000 10000 00000 10000
00000 00000 00000 10000 10000
10000 10000 10000 10000 10000
↑
MSB
Para este fin, calculamos los siguientes cuatro valores:
Fila actual: Necesitamos n bits de todos los n bits -ésimo (uno por celda), y luego cambiar a la fila actual r , recordando cada fila contiene n ² trozos:
Columna actual: necesitamos n bits cada n -²-bit (uno por fila), y luego cambiarlo a la columna actual c , recordando que cada columna contiene n bits:
Diagonal hacia adelante: necesitamos n bits cada ... (¿prestaste atención? ¡Rápido, descúbrelo!) ... n ( n +1) -ésimo bit (¡bien hecho!), Pero solo si realmente estamos en la diagonal delantera:
Diagonal hacia atrás: dos cosas aquí. Primero, ¿cómo sabemos si estamos en la diagonal hacia atrás? Matemáticamente, la condición es c = ( n - 1) - r , que es lo mismo que c = n + (- r - 1). Oye, ¿eso te recuerda algo? Así es, es un complemento de dos, por lo que podemos usar la negación bit a bit (muy eficiente en Funciton) en lugar de la disminución. En segundo lugar, la fórmula anterior supone que queremos que se establezca el bit menos significativo, pero en la diagonal hacia atrás no lo hacemos, por lo que tenemos que desplazarlo hacia arriba ... ¿sabes? ... Así es, n ( n - 1).
Este es también el único donde potencialmente dividimos entre 0 si n = 1. Sin embargo, a Funciton no le importa. 0 ÷ 0 es solo 0, ¿no lo sabes?
En el código, la función Җ
(la inferior) toma ny un índice (a partir del cual calcula r y c por división y resto), calcula estos cuatro valores y or
los une.
El algoritmo de fuerza bruta
El algoritmo de fuerza bruta es implementado por Ӂ
(la función en la parte superior). Se necesita n (el tamaño de la cuadrícula), el índice (dónde en la cuadrícula estamos colocando un número actualmente) y se toma (el número con n ³ bits que nos dice qué números todavía podemos colocar en cada celda).
Esta función devuelve una secuencia de cadenas. Cada cadena es una solución completa a la cuadrícula. Es un solucionador completo; devolvería todas las soluciones si lo deja, pero las devuelve como una secuencia de evaluación diferida.
Si el índice ha alcanzado 0, hemos completado con éxito toda la cuadrícula, por lo que devolvemos una secuencia que contiene la cadena vacía (una solución única que no cubre ninguna de las celdas). La cadena vacía es 0
, y usamos la función de biblioteca ⌑
para convertirla en una secuencia de un solo elemento.
La verificación descrita en la mejora de rendimiento a continuación ocurre aquí.
Si el índice aún no ha llegado a 0, lo disminuimos en 1 para obtener el índice en el que ahora necesitamos colocar un número (llame a eso ix ).
Usamos ♫
para generar una secuencia perezosa que contiene los valores de 0 a n - 1.
Luego usamos ɓ
(enlace monádico) con una lambda que hace lo siguiente en orden:
- Primero mire el bit relevante tomado para decidir si el número es válido aquí o no. Podemos colocar un número i si y solo si se toma & (1 << ( n × ix ) << i ) ya no está configurado. Si está configurado, devuelve
0
(secuencia vacía).
- Se usa
Җ
para calcular los bits correspondientes a la fila, columna y diagonal (s) actuales. Cambie por i y luego or
en tomado .
- Llama recursivamente
Ӂ
para recuperar todas las soluciones para las celdas restantes, pasando la nueva tomada y la ix decrementada . Esto devuelve una secuencia de cadenas incompletas; cada cadena tiene caracteres ix (la cuadrícula se rellena hasta el índice ix ).
- Use
ɱ
(map) para ver las soluciones encontradas y use ‼
para concatenar i al final de cada una. Agregue una nueva línea si el índice es un múltiplo de n , de lo contrario, un espacio.
Generando el resultado
El programa principal llama Ӂ
(el forzador bruto) con n , índice = n ² (recuerde que llenamos la cuadrícula al revés) y tomado = 0 (inicialmente no se toma nada). Si el resultado de esto es una secuencia vacía (no se encontró solución), envíe la cadena vacía. De lo contrario, envíe la primera cadena de la secuencia. Tenga en cuenta que esto significa que solo evaluará el primer elemento de la secuencia, por lo que el solucionador no continúa hasta que haya encontrado todas las soluciones.
Mejora del rendimiento
(Para aquellos que ya leyeron la versión anterior de la explicación: el programa ya no genera una secuencia de secuencias que debe convertirse por separado en una cadena para la salida; solo genera una secuencia de cadenas directamente. He editado la explicación en consecuencia Pero esa no fue la mejora principal. Aquí viene.
En mi máquina, el exe compilado de la primera versión tardó casi exactamente 1 hora en resolver n = 7. Esto no estaba dentro del límite de tiempo dado de 10 minutos, por lo que no descansé. (Bueno, en realidad, la razón por la que no descansé fue porque tuve esta idea sobre cómo acelerarlo masivamente).
El algoritmo descrito anteriormente detiene su búsqueda y retrocede cada vez que encuentra una celda en la que se establecen todos los bits en el número tomado , lo que indica que no se puede poner nada en esta celda.
Sin embargo, el algoritmo continuará llenando inútilmente la cuadrícula hasta la celda en la que se establecen todos esos bits. Sería mucho más rápido si pudiera detenerse tan pronto como cualquier celda que aún no se haya completado ya tenga todos los bits establecidos, lo que ya indica que nunca podremos resolver el resto de la cuadrícula sin importar los números que ingresemos eso. Pero, ¿cómo verifica eficientemente si alguna celda tiene sus n bits establecidos sin pasar por todos ellos?
El truco comienza agregando un solo bit por celda al número tomado . En lugar de lo que se mostró arriba, ahora se ve así:
LSB (example n=5)
↓
10000 0 00000 0 00000 0 00000 0 10000 0
00000 0 10000 0 00000 0 00000 0 10000 0
00000 0 00000 0 10000 0 00000 0 10000 0
00000 0 00000 0 00000 0 10000 0 10000 0
10000 0 10000 0 10000 0 10000 0 10000 0
↑
MSB
En lugar de n ³, ahora hay n ² ( n + 1) bits en este número. La función que llena la fila / columna / diagonal actual se ha cambiado en consecuencia (en realidad, se reescribió por completo para ser sincero). Esa función fijo pueblan sólo n bits por celda sin embargo, por el poco extra que acabamos de añadir siempre será 0
.
Ahora, digamos que estamos a la mitad del cálculo, acabamos de colocar un 1
en la celda del medio, y el número tomado se ve más o menos así:
current
LSB column (example n=5)
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0
00011 0 11110 0 01101 0 11101 0 11100 0
11111 0 11110 0[11101 0]11100 0 11100 0 ← current row
11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
↑
MSB
Como puede ver, la celda superior izquierda (índice 0) y la celda central izquierda (índice 10) ahora son imposibles. ¿Cómo determinamos esto más eficientemente?
Considere un número en el que se establece el bit 0 de cada celda, pero solo hasta el índice actual. Tal número es fácil de calcular usando la fórmula familiar:
¿Qué obtendríamos si sumamos estos dos números juntos?
LSB LSB
↓ ↓
11111 0 10010 0 01101 0 11100 0 11101 0 10000 0 10000 0 10000 0 10000 0 10000 0 ╓───╖
00011 0 11110 0 01101 0 11101 0 11100 0 ║ 10000 0 10000 0 10000 0 10000 0 10000 0 ║
11111 0 11110 0 11101 0 11100 0 11100 0 ═══╬═══ 10000 0 10000 0 00000 0 00000 0 00000 0 ═════ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ║ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 00000 0 00000 0 00000 0 00000 0 00000 0 o
↑ ↑
MSB MSB
El resultado es:
OMG
↓
00000[1]01010 0 11101 0 00010 0 00011 0
10011 0 00001 0 11101 0 00011 0 00010 0
═════ 00000[1]00001 0 00011 0 11100 0 11100 0
═════ 11111 0 11111 0 11111 0 11111 0 11111 0
11111 0 11111 0 11111 0 11111 0 11111 0
Como puede ver, la adición se desborda en el bit adicional que agregamos, ¡pero solo si se establecen todos los bits para esa celda! Por lo tanto, todo lo que queda por hacer es enmascarar esos bits (la misma fórmula que arriba, pero << n ) y verificar si el resultado es 0:
00000[1]01010 0 11101 0 00010 0 00011 0 ╓╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓─╖ ╓───╖
10011 0 00001 0 11101 0 00011 0 00010 0 ╓╜╙╖ 00000 1 00000 1 00000 1 00000 1 00000 1 ╓╜ ╙╖ ║
00000[1]00001 0 00011 0 11100 0 11100 0 ╙╥╥╜ 00000 1 00000 1 00000 0 00000 0 00000 0 ═════ ║ ║ ╓─╜
11111 0 11111 0 11111 0 11111 0 11111 0 ╓╜╙╥╜ 00000 0 00000 0 00000 0 00000 0 00000 0 ═════ ╙╖ ╓╜ ╨
11111 0 11111 0 11111 0 11111 0 11111 0 ╙──╨─ 00000 0 00000 0 00000 0 00000 0 00000 0 ╙─╜ o
Si no es cero, la cuadrícula es imposible y podemos detenernos.