REGXY, 53 49 bytes
Uses REGXY, a regex substitution based language
//$'#/
/.(.+)#\1\K/#/
a/(#).(.*#)|#.*/$'$1$2/
//a
Overview:
A number of regular expressions are applied. An example run would look like:
onion (input)
onion#onion (line 1 regex)
onion#on#ion (line 2 regex - find the repeated section and separate with #)
onionion#n#ion (line 3 regex - the length of the middle token is the garland order, remove a character and append the third token onto the original string on the left)
onionionion##ion (line 4 regex is a pointer to line 3 - repeat the previous again)
onionionion##ion (line 4 regex is a pointer to line 3 - strip everything after and including the #)
Detailed explanation
The following is a line by line breakdown of the regexes:
//$'#/
This is a regex substitution which matches the first empty string (i.e. the start of the string) and replaces it with everything to the right of the match ($'
) followed by a hash. For example, it will turn onion
into onion#onion
.
/.(.+)#\1\K/#/
This line finds the section which overlaps by looking for a group of characters immediate preceding the # ((.+)
) which are the same on the other side of the # (\1
). The \K just means 'forget that I matched anything', meaning it will not actually be replaced in the substitution. This effectively, this means we just add a # to the position after the overlap has been found, turning onion#onion
into onion#on#ion
.
a/(#).(.*#)|#.*/$'$1$2/
The initial 'a' is just a label for the regex. After this, we find the first # followed by a single character (.
) and capture everything after this until the next # (.*#
). We replace this with everything to the right of the match, i.e. the last token ($'), followed by a # ($1
), followed by the second token less a character (we treat this as a counter, decreasing it each iteration). In the case of onion#on#ion, the two tokens we backreference on are shown in brackets, and the section the entire regex matches is between the pipes: onion|(#)o(n#)|ion
. We then replace the bits we match (between the pipes) with $'
(everything to the right of the match, i.e. 'ion'), then $1 (the #), then $2 (n#), meaning we end up with onion|(ion)(#)(n#)|ion
(brackets show the three tokens in the replacement string).
If the regex fails to match in the first alternation (everything before the pipe), we must have decreased our counter to zero, meaning there are no characters inside the second token. Instead, we look at the second part of the pattern, #.*
. This simply replaces everything after the first # with $'$1$2
. Since there are no backreferences created by this alternation, and there is nothing to the right of the match (.*
matches until the end of the string), we terminate the match and return the result.
//a
This is just a pointer to the previous line, ensuring we continue to execute the regex substitution until it fails to match any more.
^(?=(.{4})).+\1$
\$\endgroup\$