Retina , 108 102 94 87 82 64 63 octets
Merci à Sp3000 de m'avoir fait suivre mon approche originale, qui faisait passer le nombre d'octets de 108 à 82.
Un gros merci à Kobi qui a trouvé une solution beaucoup plus élégante, ce qui m'a permis d'économiser 19 octets supplémentaires.
S_`(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
.
$0
m+`^(?=( *)\S.*\n\1)
<space>
Où <space>
représente un caractère d'espacement unique (qui serait sinon supprimé par SE). À des fins de comptage, chaque ligne est placée dans un fichier séparé et \n
doit être remplacée par un caractère de saut de ligne réel. Pour plus de commodité, vous pouvez exécuter le code tel quel à partir d'un seul fichier avec l' -s
indicateur.
Essayez-le en ligne.
Explication
Eh bien ... comme d'habitude, je ne peux pas vous donner une introduction complète aux groupes d'équilibrage ici. Pour un aperçu, voir ma réponse Stack Overflow .
S_`(?<=^(?<-1>.)*(?:(?<=\G(.)*).)+)
La première étape est une S
étape de pli, qui divise l’entrée en lignes de plus en plus longues. Le _
signe que les morceaux vides doivent être omis de la scission (ce qui n’affecte que la fin, car il y aura une correspondance dans la dernière position). La regex elle-même est entièrement contenue dans une apparence, elle ne correspond donc à aucun caractère, mais uniquement à des positions.
Cette partie est basée sur la solution de Kobi avec une golfitude supplémentaire que je me suis trouvée. Notez que les recherches en arrière-plan sont associées de droite à gauche dans .NET. Il est donc préférable de lire l'explication suivante de bas en haut. J'ai également inséré une autre \G
explication pour plus de clarté, bien que cela ne soit pas nécessaire pour que le motif fonctionne.
(?<=
^ # 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.
J'admire toujours le travail de Kobi ici. C'est encore plus élégant que la regex de test principal. :)
Passons à l'étape suivante:
.
$0
Simple: insérez un espace après chaque caractère sans saut de ligne.
m+`^(?=( *)\S.*\n\1)
<space>
Cette dernière étape indente correctement toutes les lignes pour former le triangle. Le m
n'est que le mode multiligne habituel pour faire ^
correspondre le début d'une ligne. Le +
dit à Retina de répéter cette étape jusqu'à ce que la chaîne cesse de changer (ce qui, dans ce cas, signifie que l'expression régulière ne correspond plus).
^ # 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.
)
Cela correspond donc au début de toute ligne qui n'a pas d'indentation plus grande que la suivante. Dans une telle position, nous insérons un espace. Ce processus se termine, une fois que les lignes sont disposées dans un triangle ordonné, car il s’agit de la disposition minimale dans laquelle chaque ligne a un retrait plus important que le suivant.