The Ward-Looms of Ostrevane
The Spinners of Ostrevane weave the warding tapestries that hang across the high gates against whatever walks down from the mountains. Each tapestry row is a sequence of threads, and each thread has a colour written as a lowercase letter a–z. The pattern-books were recopied by many hands in many shorthands, and the ward only holds if every row is rewritten in the Spinners' one canonical shorthand. Rows copied with a broken shorthand must be flagged so they can be rejected.
Write canonicalWeave(row), where row is a string written in the shorthand below. Return its canonical form as a string, or null (TypeScript) / None (Python) if row is not valid shorthand.
Valid shorthand. A row is a sequence of zero or more *items*, written back to back with nothing between them. Each item is one of: - a thread: an optional count followed by one lowercase letter a–z. It means that many threads of that colour. - a group: an optional count followed by (, then one or more threads, then ). It means the group's contents repeated that many times.
A missing count means 1. A count is one or more decimal digits 0–9 with no leading zero, so its value is at least 1 (0 and 07 are invalid). Groups may not contain other groups, and a group must contain at least one thread. A count must be followed directly by a letter or by (. Any other character (uppercase letters, spaces, punctuation), an unmatched ( or ), or a count with nothing valid after it makes the whole row invalid.
Canonical form. Expand the row into its full sequence of threads. Split that sequence into maximal runs of consecutive threads of the same colour, including runs that cross item and group boundaries. Write each run as follows: - length 1: the letter alone; - length 2: the letter twice; - length 3 or more: the length in decimal, with no leading zeros, followed by the letter.
The canonical form is these run encodings joined in order. A valid empty row has the canonical form "".
examples
- case 1
row of "2(ab)bb""aba3b" (the row expands to a b a b b b, and the group's last b joins the two after it) - case 2
row of "r2r(rb)""4rb" - case 3
row of "1r""r" (a count of one is allowed, but it is never written in canonical form) - case 4
row of "2(a(b))"null / None (groups may not contain groups)
- reward
- 540 XP
- signature
canonicalWeave
TypeScript, run as ES2022 — types are stripped, not checked. If a JS engine would run it, so will we.
- plain triple becomes counted
- count merges with bare letter
- count of one and pair
- repeated group of singles
- merge across group edges
- group repetitions merge together
- counted thread inside group
- group with pair inside
- example from notice
- second example
- empty row
- zero count invalid
- leading zero invalid
- digits alone invalid
- trailing count invalid
- nested group invalid
- double nested invalid
- empty group invalid
- unclosed group invalid
- stray close invalid
- uppercase invalid
- space invalid
- count without letter in group invalid
- multi-digit count kept
- multi-digit counts merge
- mixed merges
- adjacent groups
- three single groups merge
- group with count one
- hundred threads