The Philter-Scripts of Thornmere Abbey
Each autumn the barrow-wights walk the moor below Thornmere Abbey, and the Sisters hold them back with warding philters. Every philter is written as a terse philter-script naming the essences to be ground into it. A mis-copied script ruins a batch, so the Abbess pays for every script to be checked and reduced to a single canonical tally before the mortars are filled.
Write tallyEssences(script). It takes one string, script, and returns a string.
What makes a script valid. A valid script follows these rules exactly. Any other character, including spaces, makes it invalid.
- A script is one or more parts separated by single
+characters. There must be a part on both sides of every+, so the script cannot be empty. - A part is an optional coefficient followed by one or more units.
- A unit is either an essence or a group. Either may be followed by an optional multiplier.
- An essence is one uppercase letter
A-Zfollowed by zero or more lowercase lettersa-z. Its name is that whole run of letters.SaltBoneis two essences,SaltandBone. - A group is
(followed by one or more units and then), or[followed by one or more units and then]. Groups may nest. Every bracket must be closed by the matching kind, in properly nested order. An empty group such as()is invalid. - A coefficient or multiplier is a run of one or more digits
0-9whose first digit is not0, read as a positive base-10 integer.0,01and007are never valid numbers. - A unit with no multiplier counts once. A part with no coefficient counts once.
What it is worth. Each essence occurrence contributes the product of these numbers:
- its own multiplier,
- the multiplier of every group enclosing it,
- the coefficient of its part.
The tally of an essence is the sum of its contributions across the whole script, over all parts.
What to return.
- If the script is invalid, return exactly
"INVALID". - Otherwise, return every essence that appears, in ascending order of name, concatenated with no separators. Each name is followed by its total tally written in base 10. The tally is omitted when it is exactly 1.
- Names are compared character by character by character code, so a name sorts before any longer name that begins with it. For example,
Esorts beforeEmber.
examples
- case 1
script of "2(SaltBone)3+Mist""Bone6MistSalt6" (Salt and Bone are 1 x 3 x 2 = 6 each; Mist counts once, so no number) - case 2
script of "[Ash(EmberMist2)2]3""Ash3Ember6Mist12" - case 3
script of "EmberE2""E2Ember" (E sorts before Ember, the longer name it begins) - case 4
script of "(Ash+Mist)""INVALID" (a + may not appear inside an open bracket)
- reward
- 540 XP
- signature
tallyEssences
TypeScript, run as ES2022 — types are stripped, not checked. If a JS engine would run it, so will we.
- simple essences sorted
- coefficient and group multiplier
- nested mixed brackets
- merge across parts
- mismatched brackets
- empty group
- leading zero multiplier
- zero multiplier
- zero coefficient
- trailing plus
- empty script
- coefficient alone
- empty first part
- digit after opening bracket
- lowercase start
- single letter essence
- explicit multiplier of one
- unclosed bracket
- stray closing bracket
- plus inside group
- prefix name ordering
- multi-digit numbers
- space is invalid
- repeated essence in one part
- double plus
- deep nesting with coefficient