Retina , 108 102 94 87 82 64 63 байта
Спасибо Sp3000 за то, что он заставил меня следовать моему первоначальному подходу, благодаря которому количество байтов уменьшилось со 108 до 82.
Огромное спасибо Коби, который нашел гораздо более элегантное решение, которое позволило мне сэкономить еще 19 байтов.
S_`(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
.
$0
m+`^(?=( *)\S.*\n\1)
<space>
Где <space>
представляет один пробел (который в противном случае был бы удален SE). Для целей подсчета каждая строка помещается в отдельный файл и \n
должна быть заменена реальным символом перевода строки. Для удобства вы можете запустить код как есть из одного файла с -s
флагом.
Попробуйте онлайн.
объяснение
Ну ... как обычно, я не могу дать полное представление о балансирующих группах здесь. Для начинающих см. Мой ответ переполнения стека .
S_`(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
Первая ступень представляет собой S
плоскую стадию, которая разбивает входные данные на линии увеличивающейся длины. _
Указывает на то, что пустые куски должны быть исключены из расщепления (которое влияет только на конец, потому что там будет матч в последней позиции). Само регулярное выражение полностью содержится в осмотре, поэтому оно не будет совпадать ни с какими символами, а только с позициями.
Эта часть основана на решении Коби с некоторой дополнительной игрой в гольф, которую я нашел сам. Обратите внимание на то, что lookbehinds совпадают справа налево в .NET, поэтому следующее объяснение лучше всего читать снизу вверх. Я также вставил другой \G
в объяснение для ясности, хотя это не обязательно для работы шаблона.
(?<=
^ # 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.
Я все еще восхищаюсь работой Коби здесь. Это даже более элегантно, чем регулярное регулярное выражение. :)
Давайте перейдем к следующему этапу:
.
$0
Просто: вставьте пробел после каждого символа перевода строки.
m+`^(?=( *)\S.*\n\1)
<space>
На последнем этапе правильно выравнивают все линии, образуя треугольник. Это m
просто обычный многострочный режим для ^
сопоставления начала строки. +
Говорит Retina повторить этот этап , пока строка не перестает меняться (что, в данном случае означает , что регулярное выражение больше не соответствует).
^ # 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.
)
Так что это соответствует началу любой строки, которая не имеет большего отступа, чем следующая. В любой такой позиции мы вставляем пробел. Этот процесс завершается, когда линии располагаются в аккуратном треугольнике, потому что это минимальный макет, где каждая строка имеет больший отступ, чем следующая.