Fisión , 1328 989 887 797 bytes
Esta respuesta es un poco irrazonablemente larga (desearía que tuviéramos regiones colapsables ) ... ¡por favor, no se olvide de pasar esto y mostrarles amor a las otras respuestas!
Trabajar en este código fue lo que inspiró este desafío. Quería agregar una respuesta en Fission a EOEIS, lo que me llevó a esta secuencia. Sin embargo, aprender realmente la fisión e implementar esto tomó algunas semanas trabajando en ello de vez en cuando. Mientras tanto, la secuencia realmente había crecido en mí, así que decidí publicar un desafío por separado (además, de todos modos, esto no habría sido particularmente lejano en EOEIS).
Así que les presento, la monstruosidad:
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
9\ ; 7A9
SQS {+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \ D /8/
~4X /A@[ %5 /; & K } [S//~KSA /
3 \ A$@S S\/ \/\/\/ \/>\ /S]@A / \ { +X
W7 X X /> \ +\ A\ / \ /6~@/ \/
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
; \@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
W /MJ $$\\ /\7\A /;7/\/ /
4}K~@\ &] @\ 3/\
/ \{ }$A/1 2 }Y~K <\
[{/\ ;@\@ / \@<+@^ 1;}++@S68
@\ <\ 2 ; \ /
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
Espera que no haya una nueva línea final en la entrada, por lo que es posible que desee llamarlo así echo -n 120 | ./Fission oeis256504.fis
.
El diseño probablemente aún podría ser más eficiente, por lo que creo que todavía hay mucho margen de mejora aquí (por ejemplo, esto contiene 911 581 461 374 espacios).
Antes de llegar a la explicación, una nota sobre cómo probar esto: el intérprete oficial no funciona del todo como está. a) Mirror.cpp
no se compila en muchos sistemas. Si se encuentra con ese problema, simplemente comente la línea ofensiva: el componente afectado (un espejo aleatorio) no se usa en este código. b) Hay un par de errores que pueden conducir a un comportamiento indefinido (y probablemente lo harán para un programa tan complejo). Puede aplicar este parche para solucionarlos. Una vez que haya hecho eso, debería poder compilar el intérprete con
g++ -g --std=c++11 *.cpp -o Fission
Dato curioso: este programa utiliza casi todos los componentes que Fission tiene para ofrecer, excepto #
(espejo aleatorio), :
(medio espejo) -
o |
(espejo simple) y "
(modo de impresión).
¿Qué en la tierra?
Advertencia: Esto será bastante largo ... Supongo que está realmente interesado en cómo funciona Fission y cómo se podría programar en él. Porque si no lo estás, no estoy seguro de cómo podría resumir esto. (Sin embargo, el siguiente párrafo ofrece una descripción general del lenguaje).
La fisión es un lenguaje de programación bidimensional, donde los datos y el flujo de control están representados por átomos que se mueven a través de una cuadrícula. Si has visto o usado Marbelous antes, el concepto debería ser vagamente familiar. Cada átomo tiene dos propiedades enteras: una masa no negativa y una energía arbitraria. Si la masa se vuelve negativa, el átomo se elimina de la red. En la mayoría de los casos, puede tratar la masa como el "valor" del átomo y la energía como algún tipo de metapropiedad que utilizan varios componentes para determinar el flujo de los átomos (es decir, la mayoría de los tipos de interruptores dependen del signo de la energía). Denotaré átomos por (m,E)
, cuando sea necesario. Al comienzo del programa, la cuadrícula comienza con un montón de(1,0)
átomos de donde sea que coloque uno de los cuatro componentes UDLR
(donde la letra indica la dirección en la que se mueve inicialmente el átomo). Luego, el tablero se llena con un montón de componentes que cambian la masa y la energía de los átomos, cambian sus direcciones o hacen otras cosas más sofisticadas. Para obtener una lista completa, vea la página de esolangs , pero presentaré la mayoría de ellos en esta explicación. Otro punto importante (que el programa utiliza varias veces) es que la cuadrícula es toroidal: un átomo que golpea cualquiera de los lados vuelve a aparecer en el lado opuesto, moviéndose en la misma dirección.
Escribí el programa en varias partes más pequeñas y las ensamblé al final, así es como explicaré la explicación.
atoi
Este componente puede parecer poco interesante, pero es agradable y simple y me permite presentar muchos de los conceptos importantes del flujo aritmético y de control de Fission. Por lo tanto, pasaré por esta parte con un detalle bastante meticuloso, para poder reducir las otras partes a la introducción de nuevas mecánicas de fisión y señalar componentes de nivel superior cuyo flujo de control detallado debería poder seguir usted mismo.
La fisión solo puede leer valores de bytes de caracteres individuales, no números enteros. Si bien es una práctica aceptable por aquí, pensé que mientras lo hacía, podría hacerlo bien y analizar enteros reales en STDIN. Aquí está el atoi
código:
;
R'0@+\
/ Y@</ /[@ Y]_L
[? % \ / \ J
\$@ [Z/;[{+++++++++L
UR+++++++++>/;
O
Dos de los componentes más importantes en la fisión son los reactores de fisión y fusión. Los reactores de fisión son cualquiera de V^<>
(el código anterior usa <
y >
). Un reactor de fisión puede almacenar un átomo (enviándolo a la cuña del personaje), por defecto (2,0)
. Si un átomo golpea el ápice del personaje, se enviarán dos átomos nuevos a los lados. Su masa se determina dividiendo la masa entrante por la masa almacenada (es decir, dividiendo a la mitad por defecto): el átomo izquierdo obtiene este valor y el átomo derecho obtiene el resto de la masa (es decir, la masa se conserva en la fisión) . Ambos átomos salientes tendrán la energía entrante menosLa energía almacenada. Esto significa que podemos usar reactores de fisión para aritmética, tanto para sustracción como para división. Si se golpea un reactor de fisión desde el sitio, el átomo simplemente se refleja diagonalmente y luego se moverá en la dirección del ápice del personaje.
Los reactores de fusión son cualquiera de YA{}
(el código anterior usa Y
y {
). Su función es similar: pueden almacenar un átomo (predeterminado (1,0)
) y cuando se golpea desde el vértice se enviarán dos átomos nuevos a los lados. Sin embargo, en este caso los dos átomos serán idénticos, siempre retendrán la energía entrante y multiplicarán la masa entrante por la masa almacenada. Es decir, por defecto, el reactor de fusión simplemente duplica cualquier átomo que golpea su ápice. Cuando se golpean desde los lados, los reactores de fusión son un poco más complicados: el átomo también esalmacenado (independientemente de la otra memoria) hasta que un átomo golpee el lado opuesto. Cuando eso sucede, se libera un nuevo átomo en la dirección del ápice cuya masa y energía son la suma de los dos átomos antiguos. Si un nuevo átomo golpea el mismo lado antes de que un átomo coincidente llegue al lado opuesto, el átomo antiguo simplemente se sobrescribirá. Los reactores de fusión se pueden usar para implementar la suma y la multiplicación.
Otro componente simple que quiero sacar del camino es [
y ]
que simplemente establece la dirección del átomo a derecha e izquierda, respectivamente (independientemente de la dirección entrante). Los equivalentes verticales son M
(abajo) y W
(arriba) pero no se usan para el atoi
código. UDLR
También actúan como WM][
después de liberar sus átomos iniciales.
De todos modos, veamos el código allí arriba. El programa comienza con 5 átomos:
- El
R
y L
en la parte inferior simplemente consigue que su incremento de masa (con +
) se convierta (10,0)
y luego se almacene en un reactor de fusión y de fisión, respectivamente. Utilizaremos estos reactores para analizar la entrada de base 10.
- En
L
la esquina superior derecha se reduce su masa (con _
) (0,0)
y se almacena en el lado de un reactor de fusión Y
. Esto es para hacer un seguimiento del número que estamos leyendo: aumentaremos gradualmente y lo multiplicaremos a medida que leamos números.
- En
R
la esquina superior izquierda, su masa se establece en el código de caracteres de 0
(48) con '0
, luego la masa y la energía se intercambian @
y finalmente la masa se incrementa una vez con +
para dar (1,48)
. Luego se redirige con espejos diagonales \
y /
se almacena en un reactor de fisión. Usaremos la 48
sustracción for para convertir la entrada ASCII en los valores reales de los dígitos. También tuvimos que aumentar la masa 1
para evitar la división por 0
.
- Finalmente, la
U
esquina inferior izquierda es lo que realmente pone todo en movimiento y se usa inicialmente solo para controlar el flujo.
Después de ser redirigido a la derecha, el átomo de control golpea ?
. Este es el componente de entrada. Lee un carácter y establece la masa del átomo al valor ASCII leído y la energía a 0
. Si golpeamos EOF, la energía se establecerá en 1
.
El átomo continúa y luego golpea %
. Este es un interruptor de espejo. Para que la energía no positivo, esto actúa como un /
espejo. Pero para la energía positiva actúa como a \
(y también disminuye la energía en 1). Entonces, mientras leemos los personajes, el átomo se reflejará hacia arriba y podremos procesar el personaje. Pero cuando terminamos con la entrada, el átomo se reflejará hacia abajo y podemos aplicar una lógica diferente para recuperar el resultado. FYI, el componente opuesto es &
.
Así que tenemos un átomo en movimiento por ahora. Lo que queremos hacer para cada personaje es leer el valor de su dígito, agregarlo a nuestro total acumulado y luego multiplicar ese total acumulado por 10 para prepararse para el siguiente dígito.
El átomo de caracteres golpea primero un reactor de fusión (predeterminado) Y
. Esto divide el átomo y usamos la copia de la izquierda como un átomo de control para volver al componente de entrada y leer el siguiente carácter. Se procesará la copia a la derecha. Considere el caso en el que hemos leído el personaje 3
. Nuestro átomo lo será (51,0)
. Intercambiamos masa y energía con @
, de modo que podamos hacer uso de la resta del próximo reactor de fisión. El reactor resta 48
la energía (sin cambiar la masa), por lo que envía dos copias de (0,3)
: la energía ahora corresponde al dígito que hemos leído. La copia saliente simplemente se descarta con ;
(un componente que simplemente destruye todos los átomos entrantes). Seguiremos trabajando con la copia anterior. Tendrás que seguir su camino a través del/
y se \
refleja un poco.
El @
justo antes de que la masa del reactor de fusión permutas y la energía de nuevo, de manera que vamos a añadir (3,0)
a nuestra total acumulado en la Y
. Tenga en cuenta que el total acumulado en sí mismo siempre tendrá 0
energía.
Ahora J
es un salto. Lo que hace es saltar cualquier átomo entrante hacia adelante por su energía. Si es así 0
, el átomo sigue moviéndose directamente. Si es 1
así, saltará una celda, si es 2
así, saltará dos celdas y así sucesivamente. La energía se gasta en el salto, por lo que el átomo siempre termina con energía 0
. Como el total acumulado no tiene energía cero, el salto se ignora por ahora y el átomo se redirige al reactor de fusión {
que multiplica su masa por 10
. La copia descendente se descarta ;
mientras que la copia ascendente se retroalimenta al Y
reactor como el nuevo total acumulado .
Lo anterior sigue repitiéndose (de una manera divertida y canalizada donde se procesan los nuevos dígitos antes de que se hagan los anteriores) hasta que lleguemos a EOF. Ahora el %
enviará el átomo hacia abajo. La idea es convertir este átomo (0,1)
ahora antes de golpear el reactor total en funcionamiento para que a) el total no se vea afectado (masa cero) yb) obtengamos una energía de 1
saltar sobre el [
. Podemos cuidar fácilmente la energía con $
, lo que incrementa la energía.
El problema es que ?
no restablece la masa cuando golpeas EOF, por lo que la masa seguirá siendo la del último personaje leído y la energía lo será 0
(porque %
disminuyó el 1
regreso a 0
). Entonces queremos deshacernos de esa masa. Para hacer eso intercambiamos masa y energía @
nuevamente.
Necesito introducir un componente más antes de terminar esta sección: Z
. Esto es esencialmente lo mismo que %
o &
. La diferencia es que permite que los átomos de energía positiva pasen directamente (mientras disminuye la energía) y desvía los átomos de energía no positiva 90 grados a la izquierda. Podemos usar esto para eliminar la energía de un átomo haciendo un bucle a través de ella Z
una y otra vez; tan pronto como la energía se haya ido, el átomo se desviará y abandonará el bucle. Ese es este patrón:
/ \
[Z/
donde el átomo se moverá hacia arriba una vez que la energía sea cero. Usaré este patrón de una forma u otra varias veces en las otras partes del programa.
Entonces, cuando el átomo abandona este pequeño bucle, será (1,0)
reemplazado (0,1)
por @
antes de golpear el reactor de fusión para liberar el resultado final de la entrada. Sin embargo, el total acumulado estará apagado por un factor de 10, porque ya lo hemos multiplicado tentativamente por otro dígito.
Entonces, ahora con energía 1
, este átomo se saltará [
y saltará al /
. Esto lo desvía en un reactor de fisión que hemos preparado para dividir por 10 y arreglar nuestra multiplicación extraña. Nuevamente, descartamos una mitad con ;
y conservamos la otra como salida (aquí representada con la O
cual simplemente imprimiría el carácter correspondiente y destruiría el átomo; en el programa completo seguimos usando el átomo).
itoa
/ \
input -> [{/\ ;@
@\ <\
$ ;}++ +++++++L
%@A{/
M \@+>/
~ @
SNR'0YK
\ A!/
Por supuesto, también necesitamos convertir el resultado a una cadena e imprimirlo. Para eso es esta parte. Esto supone que la entrada no llega antes del tick 10 más o menos, sino en el programa completo que se da fácilmente. Este bit se puede encontrar en la parte inferior del programa completo.
Este código introduce un nuevo componente de fisión muy poderoso: la pila K
. La pila está inicialmente vacía. Cuando un átomo con energía no negativa golpea la pila, el átomo simplemente es empujado hacia la pila. Cuando un átomo con energía negativa golpea la pila, su masa y energía serán reemplazadas por el átomo en la parte superior de la pila (que de este modo se abre). Sin embargo, si la pila está vacía, la dirección del átomo se invierte y su energía se vuelve positiva (es decir, se multiplica por -1
).
Bien, volviendo al código real. La idea del itoa
fragmento es tomar repetidamente el módulo de entrada 10 para encontrar el siguiente dígito mientras se divide la entrada por 10 para la próxima iteración. Esto arrojará todos los dígitos en orden inverso (de menos significativo a más significativo). Para arreglar el orden, empujamos todos los dígitos en una pila y al final los sacamos uno por uno para imprimirlos.
La mitad superior del código hace el cálculo de dígitos: el L
con las más da un 10 que clonamos y alimentamos en un reactor de fisión y un reactor de fusión para que podamos dividir y multiplicar por 10. El ciclo esencialmente comienza después de la [
esquina superior izquierda . El valor actual se divide: una copia se divide por 10, luego se multiplica por 10 y se almacena en un reactor de fisión, que luego es golpeado por la otra copia en el ápice. Esto se calcula i % 10
como i - ((i/10) * 10)
. Tenga en cuenta también que A
divide el resultado intermedio después de la división y antes de la multiplicación, de modo que podamos alimentar i / 10
la próxima iteración.
El %
aborta el bucle una vez que la variable de iteración golpea 0. Como esto es más o menos un bucle do-while, este código incluso el trabajo para la impresión 0
(sin crear ceros a la izquierda de otro tipo). Una vez que salimos del bucle, queremos vaciar la pila e imprimir los dígitos. S
es lo opuesto a Z
, por lo que es un interruptor que desviará un átomo entrante con energía no positiva 90 grados a la derecha. Por lo tanto, el átomo en realidad se mueve sobre el borde de la S
línea recta a la K
de un dígito (tenga en cuenta ~
que garantiza que el átomo entrante tenga energía -1
). Ese dígito se incrementa 48
para obtener el código ASCII del carácter de dígito correspondiente. El A
divide el dígito para imprimir una copia con!
y vuelva a alimentar la otra copia al Y
reactor para el siguiente dígito. La copia que se imprime se usa como el siguiente disparador para la pila (tenga en cuenta que los espejos también la envían alrededor del borde para golpearla M
desde la izquierda).
Cuando la pila está vacía, K
reflejará el átomo y convertirá su energía en +1
tal, que pasará directamente a través del S
. N
imprime una nueva línea (solo porque está ordenada :)). Y luego el átomo se vuelve a atravesar R'0
para terminar en el lado del Y
. Como no hay más átomos alrededor, esto nunca se lanzará y el programa finalizará.
Calcular el número de fisión: el marco
Vayamos a la carne real del programa. El código es básicamente un puerto de mi implementación de referencia de Mathematica:
fission[n_] := If[
(div =
SelectFirst[
Reverse@Divisors[2 n],
(OddQ@# == IntegerQ[n/#]
&& n/# > (# - 1)/2) &
]
) == 1,
1,
1 + Total[fission /@ (Range@div + n/div - (div + 1)/2)]
]
donde div
es el número de enteros en la partición máxima.
Las principales diferencias son que no podemos tratar con valores de medio entero en Fission, por lo que estoy haciendo muchas cosas multiplicadas por dos, y que no hay recurrencia en Fission. Para evitar esto, estoy empujando todos los enteros en una partición en una cola para que se procesen más tarde. Para cada número que procesamos, incrementaremos un contador en uno y una vez que la cola esté vacía, liberaremos el contador y lo enviaremos para que se imprima. (Una cola, Q
funciona exactamente igual K
, solo en orden FIFO).
Aquí hay un marco para este concepto:
+--- input goes in here
v
SQS ---> compute div from n D /8/
~4X | /~KSA /
3 +-----------> { +X
initial trigger ---> W 6~@/ \/
4
W ^ /
| 3
^ generate range |
| from n and div <-+----- S6
| -then-
+---- release new trigger
Los nuevos componentes más importantes son los dígitos. Estos son teletransportadores. Todos los teletransportadores con el mismo dígito pertenecen juntos. Cuando un átomo golpea cualquier teletransportador, se moverá inmediatamente al siguiente teletransportador en el mismo grupo, donde el siguiente se determina en el orden habitual de izquierda a derecha, de arriba a abajo. Estos no son necesarios, pero ayudan con el diseño (y por lo tanto un poco de golf). También existe el X
que simplemente duplica un átomo, enviando una copia hacia adelante y la otra hacia atrás.
A estas alturas, es posible que pueda resolver la mayor parte del marco usted mismo. La esquina superior izquierda tiene la cola de valores aún por procesar y se libera uno n
a la vez. Una copia de n
se teletransporta a la parte inferior porque la necesitamos al calcular el rango, la otra copia va al bloque en la parte superior que calcula div
(esta es, con mucho, la sección más grande del código). Una vez que div
se ha calculado, se duplica: una copia incrementa un contador en la esquina superior derecha, que se almacena K
. La otra copia se teletransporta a la parte inferior. Si div
fue así 1
, lo desviamos hacia arriba inmediatamente y lo usamos como un disparador para la próxima iteración, sin enquistar ningún valor nuevo. De lo contrario, usamos div
yn
en la sección en la parte inferior para generar el nuevo rango (es decir, una corriente de átomos con las masas correspondientes que posteriormente se ponen en la cola), y luego suelte un nuevo disparador después de que se haya completado el rango.
Una vez que la cola está vacía, el disparador se reflejará, pasará directamente a través de él S
y volverá a aparecer en la esquina superior derecha, donde libera el contador (el resultado final) A
, que luego se teletransporta a itoa
través de 8
.
Calcular el número de fisión: el cuerpo del bucle
Entonces, todo lo que queda son las dos secciones para calcular div
y generar el rango. La computación div
es esta parte:
;
{+L /$ \/\/\/\/\/ 5/ @ [~ &@[S\/ \
/A@[ %5 /; & K } [S/
\ A$@S S\/ \/\/\/ \/>\ /S]@A / \
X X /> \ +\ A\ / \ /
/ ~A\; +;\ /@
ZX [K / {/ / @ @ } \ X @
\AS </ \V / }SZS S/
X ;;@\ /;X /> \ ; X X
\@+ >/ }$S SZS\+; //\V
/ \\ /\; X X @ @ \~K{
\0X / /~/V\V / 0W//
\ Z [K \ //\
\ /\7\A /;7/\/
Probablemente has visto suficiente ahora para resolver esto con paciencia. El desglose de alto nivel es el siguiente: las primeras 12 columnas generan un flujo de divisores de 2n
. Las siguientes 10 columnas filtran las que no satisfacen OddQ@# == IntegerQ[n/#]
. Las siguientes 8 columnas filtran las que no satisfacen n/# > (# - 1)/2)
. Finalmente, empujamos todos los divisores válidos en una pila, y una vez que terminamos, vaciamos toda la pila en un reactor de fusión (sobrescribiendo todos menos el último / mayor divisor) y luego liberamos el resultado, seguido de eliminar su energía (que no era -cero de verificar la desigualdad).
Hay muchos caminos locos que realmente no hacen nada. Predominantemente, la \/\/\/\/
locura en la parte superior (las 5
s también son parte de ella) y un camino alrededor de la parte inferior (que pasa por las 7
s). Tuve que agregar estos para lidiar con algunas condiciones de carrera desagradables. La fisión podría usar un componente de retraso ...
El código que genera el nuevo rango n
y div
es el siguiente:
/MJ $$\
4}K~@\ &] @\ 3/\
\{ }$A/1 2 }Y~K <\
\@ / \@<+@^ 1;}++@
2 ; \ /
Primero calculamos n/div - (div + 1)/2
(ambos términos con piso, que produce el mismo resultado) y almacenamos para más adelante. Luego generamos un rango de div
abajo hacia abajo 1
y agregamos el valor almacenado a cada uno de ellos.
Hay dos nuevos patrones comunes en ambos, que debo mencionar: uno es SX
o ZX
golpear desde abajo (o versiones rotadas). Esta es una buena manera de duplicar un átomo si desea que una copia siga adelante (ya que la redirección de las salidas de un reactor de fusión a veces puede ser engorrosa). El S
o Z
gira el átomo hacia adentro X
y luego gira la copia reflejada nuevamente en la dirección original de propagación.
El otro patrón es
[K
\A --> output
Si almacenamos algún valor K
, podemos recuperarlo repetidamente golpeando K
con energía negativa desde la parte superior. El A
duplica el valor que nos interesa y envía lo que copiar de nuevo a la derecha en la pila para la próxima vez que lo necesitamos.
Bueno, eso fue bastante tomo ... pero si realmente superaste esto, espero que tengas la idea de que Fission i͝s̢̘̗̗ ͢i̟nç̮̩r̸̭̬̱͔e̟̹̟̜͟d̙i̠͙͎̖͓̯b̘̠͎̭̰̼l̶̪̙̮̥̮y̠̠͎̺͜ ͚̬̮f̟͞u̱̦̰͍n͍ ̜̠̙t̸̳̩̝o ̫͉̙͠p̯̱̭͙̜͙͞ŕ̮͓̜o̢̙̣̭g̩̼̣̝r̤͍͔̘̟ͅa̪̜͇m̳̭͔̤̞ͅ ͕̺͉̫̀ͅi͜n̳̯̗̳͇̹.̫̞̲̞̜̳