Respuestas:
*
para salidaDebido a que puede generar resultados dejando una cadena en la pila , puede ser útil acumular la cadena utilizando en *
lugar de generar con S
. Digamos que su desafío fue "tomar una cadena y agregar un espacio", la forma de hacerlo con la salida sería:
S( )S
La forma de hacerlo *
, por otro lado, es un byte más corto:
( )*
El problema es que si su salida tiene mucha acumulación, puede costar bytes lidiar con el elemento de salida en la pila.
Si necesita usar mucho un fragmento de código, tiene sentido almacenar ese código en la pila y duplicarlo y evaluarlo de vez en cuando. Hasta ahora, eso es solo la programación normal de Underload. Desafortunadamente, mantener un valor en la pila durante mucho tiempo es difícil y tiende a hacer que su código se vuelva detallado, y eso es cierto incluso si el valor es una función en lugar de datos. Esto empeora mucho si tiene varias funciones que deben reutilizarse repetidamente.
En el tipo de programa más grande que podría beneficiarse de varias funciones reutilizadas, una solución que puede usar es crear una función grande que pueda cumplir cualquiera de sus propósitos dependiendo de cómo se llame (ya sea en función de lo que está debajo de la pila, o mediante el uso de secuencias ya que sólo llamando ^
; una función cuidadosamente escrita puede distinguir ^^
del ^:^
de ^*^
de ^~^
, que le da cuatro, secuencias cortas bastante distintas). También puede almacenar otras cosas útiles, como cadenas que usa varias veces, en este "diccionario". Tenga en cuenta que si usa mucho el diccionario, puede tener sentido hacerlo una especie de quine, empujando una copia de sí mismo en la pila, para que no necesite copiarlo manualmente con:
para poder usarlo sin perder la capacidad de usarlo en el futuro.
^!!!!^
búsqueda de estilo preferida (que también he usado en varios otros ejemplos en la página, especialmente en la sección de minimización). Aunque eso podría no dar la búsqueda más corta.
Como un ejemplo simple, la implementación más común de booleanos son !()
para falso (es decir, entero 0) y la cadena nula para verdadero (es decir, entero 1), pero si tiene un problema que se basa en gran medida en XOR lógico, podría generar más tiene sentido usar la cadena nula para falso y ~
para verdadero (este formato de datos se puede convertir a cualquier otro formato booleano usando (false)~(true)~^!
, y permite la implementación muy concisa *
para XOR.
Es posible llevar este principio general aún más lejos y usar funciones que su programa necesitará más adelante como parte de sus valores de datos; eso ahorra tener que almacenar las funciones y los datos por separado en la pila. Esto puede hacer que el control de flujo sea bastante más confuso, pero cuando se juega al golf, la mantenibilidad a menudo tiene que pasar a un segundo plano, y no es que Underload sea tan útil de todos modos.
(!)
y (~!)
para booleanos, pero tu camino parece mejor.
La forma funcionalmente pura de disminuir un número de la Iglesia es usar la función predecesora del cálculo lambda:
\n.n(\p.\z.z($(pT))(pT))(\z.z0[whatever you define the predecessor of 0 to be])
Donde 0 = \ x. \ Yy, T = \ x. \ Yx, y $ es el sucesor.
Reescrito en Underload, esto es 28 bytes:
(!())~(!())~(!:(:)~*(*)*~)~^!
Esto está bien, pero podemos explotar algunas de las propiedades útiles de Underload, a saber, que :!
y ()*
no son operaciones. Esto significa que, para un número n
, :ⁿ!!()()*ⁿ
(donde cⁿ
se c
repite n
veces) produce n-1. Por ejemplo, hacer esto para el número 3 de la Iglesia produce esto:
:::!!()()***
Al eliminar los pares no operativos, obtenemos:
:*
Que es 2.
Así que esta es la operación predecesora nueva y más corta:
:(:)~^(!!()())*~(*)~^*
Esto es 7 bytes más corto.
(()~(:))~:(^!!())*~(*)~^**
todavía es 3 bytes más corto.
Underload en realidad tiene dos pilas: la pila de cadenas y la pila de comandos que forman el código fuente. Las ^
instrucciones de Underload nos permiten mover cadenas de la primera pila a la segunda. Al hacer esto, podemos ahorrar mucha manipulación innecesaria de la pila.
Por ejemplo, supongamos que tenemos (a)(b)(c)
en la pila principal y nos gustaría concatenar los dos elementos inferiores, ignorando (c)
, para obtener (ab)(c)
. La forma ingenua de hacer esto es rotar la pila para obtener (c)(a)(b)
y luego concatenar e intercambiar:
a~a~*~a*^*~
Esto es malo. Usar a~a~*~a*^
para rotar la pila de esta manera es extremadamente costoso y debe evitarse cuando sea posible. Al poner (c)
en el espacio del programa, esto se puede hacer cuatro bytes más corto:
a(*)~*^
La idea es tomar las instrucciones que le gustaría ejecutar y luego agregar una instrucción para (c)
retroceder al final, y luego evaluar el resultado. Esto significa que no tenemos que preocuparnos (c)
hasta que se retrase una vez que hayamos terminado.
(*)~a*^
, lo que creo que es un poco más composible. Esencialmente ~a*^
es el dip
comando de Joy.
eval
comando, nunca antes había visto un lenguaje como ese.