Retina , 108 102 94 87 82 64 63 bytes
Agradeço ao Sp3000 por me fazer seguir minha abordagem original, que reduziu a contagem de bytes de 108 para 82.
Um enorme agradecimento a Kobi, que encontrou uma solução muito mais elegante, o que me permitiu salvar outros 19 bytes em cima disso.
S_`(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
.
$0
m+`^(?=( *)\S.*\n\1)
<space>
Onde <space>
representa um caractere de espaço único (que seria removido pelo SE). Para fins de contagem, cada linha entra em um arquivo separado e \n
deve ser substituída por um caractere de avanço de linha real. Por conveniência, você pode executar o código como está em um único arquivo com o-s
sinalizador.
Experimente online.
Explicação
Bem ... como sempre, não posso dar uma introdução completa aos grupos de equilíbrio aqui. Para uma cartilha, veja minha resposta Stack Overflow .
S_`(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
O primeiro estágio é um S
estágio plit, que divide a entrada em linhas de comprimento crescente. o_
indica que pedaços vazias devem ser omitidos da divisão (que afeta apenas o final, porque haverá uma correspondência na última posição). O regex em si é inteiramente contido em uma olhada, para que não corresponda a nenhum caractere, mas apenas a posições.
Esta parte é baseada na solução de Kobi com um pouco de golfe adicional que eu me encontrei. Observe que os lookbehinds são correspondidos da direita para a esquerda no .NET, portanto, a melhor explicação a seguir deve ser lida de baixo para cima. Também inseri outra \G
na explicação para maior clareza, embora isso não seja necessário para o padrão funcionar.
(?<=
^ # 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.
Eu ainda estou admirando o trabalho de Kobi aqui. Isso é ainda mais elegante que o regex de teste principal. :)
Vamos para a próxima etapa:
.
$0
Simples: insira um espaço após cada caractere que não seja de avanço de linha.
m+`^(?=( *)\S.*\n\1)
<space>
Este último estágio recua todas as linhas corretamente para formar o triângulo. O m
é apenas o modo de várias linhas de costume para fazer ^
coincidir com o início de uma linha. O +
instrui o Retina a repetir esse estágio até que a string pare de mudar (o que, nesse caso, significa que a regex não corresponde mais).
^ # 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.
)
Portanto, isso corresponde ao início de qualquer linha que não tenha um recuo maior que o seguinte. Em qualquer posição, inserimos um espaço. Esse processo termina quando as linhas são organizadas em um triângulo puro, porque esse é o layout mínimo em que cada linha tem um recuo maior que o seguinte.