The Wallwright's Tally Stone
The wallwright of bitwick keeps the town wall in repair, and every cart that arrives brings a delivery slip scrawled by whoever loaded it. The guild will only carve a measure into the tally stone in one fixed form, so the slips must be rewritten before they are carved.
Measures use four units:
- 1 league = 4 rods
- 1 rod = 6 spans
- 1 span = 5 nails
Write canonicalTally(slip), which takes a string and returns a string.
Reading the slip. The slip's tokens are separated by one or more whitespace characters, and there may be leading or trailing whitespace. The slip may be empty or contain only whitespace. Otherwise it is made of:
- An optional first token
minus. If present, it negates the *entire* total of everything after it. It can appear only as the first token, and at most once. - Then zero or more terms. Each term is optionally preceded by the token
less, which negates *only that one term*. The term itself is a count token followed by a unit token. A count is an optional-followed by one or more decimal digits. A unit token is one ofleague,leagues,rod,rods,span,spans,nail,nails— singular and plural mean the same thing in the input, whatever the count is.
The slip's value, in nails, is the sum of every term's count times its unit's size in nails, with a term's sign flipped if less precedes it, and the whole sum negated if the slip begins with minus. You may assume the slip is well formed as described.
Carving the stone. Return the canonical rendering of that value:
- If the value is zero, return exactly
"0 nails". - Otherwise, take the absolute value and break it into leagues, rods, spans and nails so that the count of nails is under 5, the count of spans is under 6, and the count of rods is under 4.
- Render each unit whose count is not zero, largest unit first, as the count, a single space, and the unit name — singular when the count is exactly 1, plural otherwise. Units with a count of zero are not rendered at all.
- Join those with single spaces. If the value is negative, put the token
minusand a single space before the whole thing.
There is no other whitespace in the returned string.
examples
- case 1
slip of "3 rods 14 spans""1 league 1 rod 2 spans" — 3 rods and 14 spans is 160 nails, which is 1 league, 1 rod and 2 spans - case 2
slip of "1 league less 1 nail""3 rods 5 spans 4 nails" — 119 nails, and the zero league count is not rendered - case 3
slip of "minus 1 rod -2 spans""minus 4 spans" — the terms give 30 - 10 = 20 nails, and the leading minus negates the whole of it - case 4
slip of "1 rod less 6 spans""0 nails" — the terms cancel exactly
- reward
- 540 XP
- signature
canonicalTally
TypeScript, run as ES2022 — types are stripped, not checked. If a JS engine would run it, so will we.
- mixed slip carries into leagues
- empty slip
- terms cancel to nothing
- leading minus meets a negative count
- less drops a zero league count
- less with a negative count adds
- plural input singular output
- ragged whitespace
- exact league
- leading minus over a negative total
- minus before a zero count
- long slip skips empty spans
- canonical slip re-reads unchanged