Retina , 108 102 94 87 82 64 63 bytes
Gracias a Sp3000 por hacerme seguir mi enfoque original, que redujo el conteo de bytes de 108 a 82.
Un enorme agradecimiento a Kobi que encontró una solución mucho más elegante, lo que me permitió guardar otros 19 bytes además de eso.
S_`(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
.
$0
m+`^(?=( *)\S.*\n\1)
<space>
Donde <space>
representa un solo carácter de espacio (que de otro modo sería eliminado por SE). Para fines de conteo, cada línea va en un archivo separado y \n
debe reemplazarse con un carácter de salto de línea real. Para mayor comodidad, puede ejecutar el código tal como está desde un único archivo con la -s
bandera.
Pruébalo en línea.
Explicación
Bueno ... como siempre, no puedo dar una introducción completa a los grupos de equilibrio aquí. Para una introducción, vea mi respuesta de desbordamiento de pila .
S_`(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
La primera etapa es una S
etapa de división, que divide la entrada en líneas de longitud creciente. El _
indica que los fragmentos vacíos deben omitirse de la división (lo que solo afecta al final, porque habrá una coincidencia en la última posición). La expresión regular en sí está completamente contenida en un vistazo, por lo que no coincidirá con ningún personaje, sino solo con las posiciones.
Esta parte se basa en la solución de Kobi con algo de golfitud adicional que encontré yo mismo. Tenga en cuenta que las retrospectivas coinciden de derecha a izquierda en .NET, por lo que la siguiente explicación debería leerse de abajo hacia arriba. También he insertado otro \G
en la explicación para mayor claridad, aunque eso no es necesario para que el patrón funcione.
(?<=
^ # And we ensure that we can reach the beginning of the stack by doing so.
# The first time this is possible will be exactly when tri(m-1) == tri(n-1),
# i.e. when m == n. Exactly what we want!
(?<-1>.)* # Now we keep matching individual characters while popping from group <1>.
\G # We've now matched m characters, while pushing i-1 captures for each i
# between 1 and m, inclusive. That is, group <1> contains tri(m-1) captures.
(?:
(?<=
\G # The \G anchor matches at the position of the last match.
(.)* # ...push one capture onto group <1> for each character between here
# here and the last match.
) # Then we use a lookahead to...
. # In each iteration we match a single character.
)+ # This group matches all the characters up to the last match (or the beginning
# of the string). Call that number m.
) # If the previous match was at position tri(n-1) then we want this match
# to happen exactly n characters later.
Todavía estoy admirando el trabajo de Kobi aquí. Esto es incluso más elegante que la expresión regular de prueba principal. :)
Pasemos a la siguiente etapa:
.
$0
Simple: inserte un espacio después de cada carácter sin salto de línea.
m+`^(?=( *)\S.*\n\1)
<space>
Esta última etapa sangra todas las líneas correctamente para formar el triángulo. El m
es sólo el modo multilínea habitual para hacer ^
que coincida con el comienzo de una línea. El +
le dice a Retina que repita esta etapa hasta que la cadena deje de cambiar (lo que, en este caso, significa que la expresión regular ya no coincide).
^ # Match the beginning of a line.
(?= # A lookahead which checks if the matched line needs another space.
( *) # Capture the indent on the current line.
\S # Match a non-space character to ensure we've got the entire indent.
.*\n # Match the remainder of the line, as well as the linefeed.
\1 # Check that the next line has at least the same indent as this one.
)
Entonces esto coincide con el comienzo de cualquier línea que no tenga una sangría más grande que la siguiente. En cualquiera de esas posiciones, insertamos un espacio. Este proceso termina, una vez que las líneas se arreglan en un triángulo ordenado, porque ese es el diseño mínimo donde cada línea tiene una sangría más grande que la siguiente.