Ilegible , 2199 2145 2134 2104 2087 2084 bytes
Soporta tanto k
/ j
como ▲
/▼
sintaxis.
En una buena tradición ilegible, aquí está el programa formateado en fuente proporcional, para ofuscar la distinción entre apóstrofes y comillas dobles:
'"" "" ""' "" "" "" "" '"" "" "" "" "" "'" "" "" '"" "" "'" "" "" "' "" '""' "" '""' "" "'" "'" "" "" "" "" "" "" "" "" "" "'" ""' "" '" "'" "" "" ""' "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" '"" ""'"" ""' "" "" '"" "" ""' "" '""' "" '""' "" '"" "" "" "" "'" "" "" " '""' "" '""' "" '"" "'" "" "" "" "" "" "" "" "" "" "" "" '""' "" '" "'" ""' "" "" "" "" '"" "" "" "" "" ""' "" "" "" "" "" "" "" "" """'" "'" "'" "'" "'" "'" "" '"" "" "" ""' "" "" "" "" "" "" "" "'" " '""' "" "'" "" "" "" "'" "" "" "'" "'" "'" "'" "'" "'" "" '""' "" ' "" "'" "" "" "'" "" '"" "" "" ""' "" "" "" "" "" "" "" "" "" "" "'"" "'" "" "" "" "" "" "" "" "'" "" "" "" '"" "" "" "" "" "" "" ""' " "" '"" "" "" "" "" ""' "" "" "" "" "" '"" "" "" "" "" "" "" "" "" "" "" "" '"" "'" "" '""' "" "" "" "" "" "" "" "" "" "" "" "" "" "" "'""' "" '""' "" '""' "" "'" ""' "" '""' "" "'" "" "" "" "" "'" "" "" "" "" '"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" '""' "" '""' "" '"" "" "" "" ""' "" "'" "'" "'" ""' "" '"""'" "" "" "" ""' "" "" "" "" "" '"" ""' "" '""' "" "" "" "" "" "" '" "" '"" "" "'" "" "" "'" "" "" "" "" "" "" "" "" "" "" ""' " "'" "'" "'" "'" "" '"" "" "" "" "" "" "" "" ""' "" "" "" '""' ""'""' "" "'" "" "" "'" "'" "'" "'" ""' "" '"" "" "" "" "" ""' "" '"" "'" ""' "" "" "" '""' "" '"" "'" "'" "" "" "" "" "" "" "" "" "" "" " '""' "" "'" "" "" "" "'" "" "" "'" "'" "" '"" "" "" "" "" "'" "" '"" "" "" "" '"" "" "" "'" "" "" "" "" "" "'" "" "" "" "" ""' "" "" "" '"" "" "" ""' "" "" "" "'" "'" "'" "" "" "" "" "" "" "" "" "" "" "'" "'" "'" "" '"" "" ""' "" '""' "" "'" "" "" "" "" "" "" "" "" "" "'"" "'" "" '"" "" ""' "" '"" "'" "'" "" "" "" "" "" "" "" "" "" "" "" '"" "" "" "'" "" "" "" "'" "" "" "" "" "" "" "" "" "" "" "" "" "" "" " "" "" '"" "" ""' "" '""' "" '"" "'" "'" "" "" "" "" "" "'" "'" ""'"" ""' "" "" "" "" "'" "" "" "" "" ""' "" "" "" "" "" "" "'" "" "" " '""' "" '"" "'" "'" "" "" ""' "" '""' "" "'" "" "" "" "" "" "" "" "" "" "'" "'" "" '""' "" "" "" "" "" "" "'" "" "'" "" "" "" "" "'" "'"" "" "" "'" "" "" "" "" ""' "" "" "" "" "" "" "" "" "" "" "'" "" "" "'" ""' "" "" "" "" '"" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" '"" "'" "'" "" "" ""' "" "" "" "" "" '"" "" "" "" "" "" "" "" "'"" '""' "" "'" "" "" "" "" "" "" ""' "" '"" "" "" "" ""' "" "" "" "" "'" "" "" "'" "'" ""' "" '""' "" '""' "" '"" "" "" "" "'" "'" "'" " '""' "" "'" "" "" "" "" "" ""' "" "" "" "" "" "" "" "" "" "" "" """'" "" "" "" "'" "" "" "'" "" "" "" "" ""' "" "" "" "" "" "" "" "" '" "" "" "" '"" "" "" ""' "" "" "" "" "" "" "" "" "" "" "" "" "" "" "'" "" '""' "" '"" "" "" "'" "" "" "" "" "" '"" "" "" "" "" "" "" """'" ""' "" '""' "" '""' "" '""' "" '""' "" '""' "" '""' "" '""' "" '""' "" '""' "" '""' "" '""' "" "" "" "" "" "" "" "" "" "" '"" "" " "" '"" "'" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" """" "" "'" "'" ""
Este fue un desafío increíble. ¡Gracias por tu publicación!
Explicación
Para tener una idea de lo que ilegible puede y no puede hacer, imagine Brainfuck con una cinta infinita en ambas direcciones, pero en lugar de un puntero de memoria moviendo una celda a la vez, puede acceder a cualquier celda de memoria desreferenciando un puntero. Esto resulta bastante útil en esta solución, aunque otras operaciones aritméticas, incluido el módulo, deben realizarse a mano.
Aquí está el programa como pseudocódigo con comentarios del director:
// Initialize memory pointer. Why 5 will be explained at the very end!
ptr = 5
// FIRST PASS:
// Read all characters from stdin, store them in memory, and also keep track of the
// current line number at each character.
// We need the +1 here so that EOF, which is -1, ends the loop. We increment ptr by 2
// because we use two memory cells for each input character: one contains the actual
// character (which we store here); the other will contain the line number at which the
// character occurs (updated at the end of this loop body).
while ch = (*(ptr += 2) = read) + 1:
// At this point, ch will be one more than the actual value.
// However, the most code-economical way for the following loop is to
// decrement inside the while condition. This way we get one fewer
// iteration than the value of ch. Thus, the +1 comes in handy.
// We are now going to calculate modulo 4 and 5. Why? Because
// the mod 4 and 5 values of the desired input characters are:
//
// ch %5 %4
// ^ 1
// v 2
// k 3
// j 4
// ▲ 0 2
// ▼ 0 0
//
// As you can see, %5 allows us to differentiate all of them except ▲/▼,
// so we use %4 to differentiate between those two.
mod4 = 0 // read Update 2 to find out why mod5 = 0 is missing
while --ch:
mod5 = mod5 ? mod5 + 1 : -4
mod4 = mod4 ? mod4 + 1 : -3
// At the end of this loop, the value of mod5 is ch % 5, except that it
// uses negative numbers: -4 instead of 1, -3 instead of 2, etc. up to 0.
// Similarly, mod4 is ch % 4 with negative numbers.
// How many lines do we need to go up or down?
// We deliberately store a value 1 higher here, which serves two purposes.
// One, as already stated, while loops are shorter in code if the decrement
// happens inside the while condition. Secondly, the number 1 ('""") is
// much shorter than 0 ('""""""""'""").
up = (mod5 ? mod5+1 ? mod5+3 ? 1 : 3 : 2 : mod4 ? 3 : 1)
dn = (mod5 ? mod5+2 ? mod5+4 ? 1 : 3 : 2 : mod4 ? 1 : 3)
// As an aside, here’s the reason I made the modulos negative. The -1 instruction
// is much longer than the +1 instruction. In the above while loop, we only have
// two negative numbers (-3 and -4). If they were positive, then the conditions in
// the above ternaries, such as mod5+3, would have to be mod5-3 etc. instead. There
// are many more of those, so the code would be longer.
// Update the line numbers. The variables updated here are:
// curLine = current line number (initially 0)
// minLine = smallest linenum so far, relative to curLine (always non-positive)
// maxLine = highest linenum so far, relative to curLine (always non-negative)
// This way, we will know the vertical extent of our foray at the end.
while --up:
curLine--
minLine ? minLine++ : no-op
maxLine++
while --dn:
curLine++
minLine--
maxLine ? maxLine-- : no-op
// Store the current line number in memory, but +1 (for a later while loop)
*(ptr + 1) = curLine + 1
// At the end of this, minLine and maxLine are still relative to curLine.
// The real minimum line number is curLine + minLine.
// The real maximum line number is curLine + maxLine.
// The total number of lines to output is maxLine - minLine.
// Calculate the number of lines (into maxLine) and the real minimum
// line number (into curLine) in a single loop. Note that maxLine is
// now off by 1 because it started at 0 and thus the very line in which
// everything began was never counted.
while (++minLine) - 1:
curLine--
maxLine++
// Make all the row numbers in memory positive by adding curLine to all of them.
while (++curLine) - 1:
ptr2 = ptr + 1
while (ptr2 -= 2) - 2: // Why -2? Read until end!
*ptr2++
// Finally, output line by line. At each line, we go through the memory, output the
// characters whose the line number is 0, and decrement that line number. This way,
// characters “come into view” in each line by passing across the line number 0.
while (--maxLine) + 2: // +2 because maxLine is off by 1
ptr3 = 5
while (ptr -= 2) - 5:
print (*((ptr3 += 2) + 1) = *(ptr3 + 1) - 1) ? 32 : *ptr3 // 32 = space
ptr = ptr3 + 2
print 10 // newline
Esto en cuanto a la lógica del programa. Ahora tenemos que traducir esto a ilegible y usar algunos trucos de golf más interesantes.
Las variables siempre se desreferencian numéricamente en No legible (por ejemplo, se a = 1
convierte en algo parecido *(1) = 1
). Algunos literales numéricos son más largos que otros; el más corto es 1, seguido de 2, etc. Para mostrar cuánto más largos son los números negativos, aquí están los números de -1 a 7:
-1 '""""""""'""""""""'""" 22
0 '""""""""'""" 13
1 '""" 4
2 '""'""" 7
3 '""'""'""" 10
4 '""'""'""'""" 13
5 '""'""'""'""'""" 16
6 '""'""'""'""'""'""" 19
7 '""'""'""'""'""'""'""" 22
Claramente, queremos asignar la variable # 1 a la que ocurre con mayor frecuencia en el código. En el primer ciclo while, esto es definitivamente mod5
, que aparece 10 veces. Pero no necesitamos mod5
más después del primer ciclo while, por lo que podemos reasignar la misma ubicación de memoria a otras variables que usaremos más adelante. Estos son ptr2
y ptr3
. Ahora se hace referencia a la variable 21 veces en total. (Si está tratando de contar el número de ocurrencias usted mismo, recuerde contar algo comoa++
dos veces, una para obtener el valor y otra para establecerlo).
Solo hay otra variable que podemos reutilizar; después de calcular los valores del módulo, ch
ya no es necesario. up
y dn
aparece el mismo número de veces, así que cualquiera está bien. Fusionémonos ch
con up
.
Esto deja un total de 8 variables únicas. Podríamos asignar las variables 0 a 7 y luego iniciar el bloque de memoria (que contiene los caracteres y los números de línea) en 8. ¡Pero! Como 7 tiene la misma longitud en el código que −1, también podríamos usar las variables −1 a 6 e iniciar el bloque de memoria en 7. ¡De esta manera, cada referencia a la posición de inicio del bloque de memoria es ligeramente más corta en el código! Esto nos deja con las siguientes tareas:
-1 dn
0 ← ptr or minLine?
1 mod5, ptr2, ptr3
2 curLine
3 maxLine
4 ← ptr or minLine?
5 ch, up
6 mod4
7... [data block]
Ahora esto explica la inicialización en la parte superior: es 5 porque es 7 (el comienzo del bloque de memoria) menos 2 (el incremento obligatorio en la primera condición while). Lo mismo ocurre con las otras dos ocurrencias de 5 en el último bucle.
Tenga en cuenta que, dado que 0 y 4 tienen la misma longitud en el código, ptr
y minLine
podrían asignarse de cualquier manera. ... ¿O podrían?
¿Qué pasa con el misterioso 2 en el penúltimo bucle while? ¿No debería ser este un 6? Solo queremos disminuir los números en el bloque de datos, ¿verdad? Una vez que llegamos a 6, estamos fuera del bloque de datos y debemos detenernos. ¡Sería una vulnerabilidad de seguridad de error de error de desbordamiento de búfer!
Bueno, piensa en lo que pasa si no nos detenemos. Decrementamos las variables 6 y 4. La variable 6 es mod4
. Eso solo se usa en el primer ciclo while y ya no es necesario aquí, por lo que no se hace daño. ¿Qué pasa con la variable 4? ¿Qué crees que debería ser ptr
o debería ser la variable 4 minLine
? Así es, ¡ minLine
ya no se usa en este momento! Por lo tanto, la variable # 4 es minLine
y podemos disminuirla de manera segura y no hacer daño.
ACTUALIZACIÓN 1! Golfed desde 2199 a 2145 bytes por darse cuenta de que dn
puede también fusionarse con mod5
, a pesar de que mod5
todavía se utiliza en el cálculo del valor de dn
! La nueva asignación de variables es ahora:
0 ptr
1 mod5, dn, ptr2, ptr3
2 curLine
3 maxLine
4 minLine
5 ch, up
6 mod4
7... [data block]
ACTUALIZACIÓN 2! Golfó de 2145 a 2134 bytes al darse cuenta de que, dado mod5
que ahora está en la misma variable que dn
, que se cuenta a 0 en un ciclo while, mod5
ya no necesita inicializarse explícitamente a 0.
ACTUALIZACIÓN 3! Golfó de 2134 a 2104 bytes al darse cuenta de dos cosas. Primero, aunque la idea del "módulo negativo" valió la pena mod5
, el mismo razonamiento no se aplica mod4
porque nunca probamos, mod4+2
etc. Por lo tanto, cambiar mod4 ? mod4+1 : -3
a mod4 ? mod4-1 : 3
nos lleva a 2110 bytes. En segundo lugar, dado mod4
que siempre es 0 o 2, podemos inicializar mod4
a 2 en lugar de 0 e invertir los dos ternarios (en mod4 ? 3 : 1
lugar de mod4 ? 1 : 3
).
ACTUALIZACIÓN 4! Jugó desde 2104 hasta 2087 bytes al darse cuenta de que el ciclo while que calcula los valores del módulo siempre se ejecuta al menos una vez, y en tal caso, Ilegible le permite reutilizar el valor de la última declaración en otra expresión. Por lo tanto, en lugar de while --ch: [...]; up = (mod5 ? mod5+1 ? [...]
ahora tenemos up = ((while --ch: [...]) ? mod5+1 ? [...]
(y dentro de ese ciclo while, calculamos mod4
primero, así que esa mod5
es la última declaración).
ACTUALIZACIÓN 5! Golfé de 2087 a 2084 bytes al darme cuenta de que en lugar de escribir las constantes 32
y 10
(espacio y nueva línea), puedo almacenar el número 10 en la variable (ahora no utilizada) # 2 (llamémosla ten
). En lugar de ptr3 = 5
escribir ten = (ptr3 = 5) + 5
, luego se 32
vuelve ten+22
y se print 10
vuelve print ten
.