The Toll-Book of Marrowgate
# The Toll-Book of Marrowgate
The warden of Marrowgate bridge prices every traveller out of a toll-book, and the book's later pages contradict its earlier ones on purpose.
A traveller is an object mapping attribute names to string values (things like sort, cart, guild). An attribute a traveller does not carry is simply absent. No traveller's value is the string "*".
A rule is an object with three fields:
when— an object mapping attribute names to strings. An entryk: "*"is satisfied when the traveller has attributekat all, whatever its value. An entryk: vwherevis not"*"is satisfied when the traveller has attributekand its value is exactlyv. A rule matches a traveller when every entry of itswhenis satisfied; an emptywhenmatches every traveller.kind— one of"toll","extra","pass".amount— an integer, possibly negative. For a"pass"rule it is ignored.
The specificity of a rule is the number of entries in its when whose value is not "*".
For a single traveller, considering only the rules that match that traveller:
1. The winning toll rule is the matching "toll" rule of greatest specificity; if several are tied, the one latest in rules wins. If no "toll" rule matches, the starting price is base. 2. If at least one "pass" rule matches, the traveller pays 0 — unless a "toll" rule won and its specificity is strictly greater than the specificity of every matching "pass" rule. 3. Otherwise the traveller pays the winning toll rule's amount (or base if no "toll" rule matched), plus the amount of every matching "extra" rule. 4. A toll is never negative: any total below 0 is charged as 0.
Write tollFor(rules, travellers, base), where rules is a list of rules, travellers is a list of travellers, and base is an integer. It returns a list of integers: the price of each traveller, in the order the travellers were given.
examples
- case 1
rules: [ {when: {}, kind: "toll", amount: 4}, {when: {cart: "*"}, kind: "toll", amount: 9} ]; travellers: [ {sort: "merchant"}, {sort: "drover", cart: "wool"} ]; base of 100[4, 9] — the merchant carries no cart attribute at all, so the wildcard rule does not match him; the drover matches both rules at specificity 0, and the later one wins - case 2
rules: [ {when: {cargo: "iron"}, kind: "toll", amount: 30}, {when: {sort: "herald"}, kind: "pass", amount: 0} ]; travellers: [ {sort: "herald", cargo: "iron"} ]; base of 10[0] — both match at specificity 1, and a pass is overridden only by a strictly more specific toll rule - case 3
rules: [ {when: {}, kind: "toll", amount: 8}, {when: {guild: "beggar"}, kind: "extra", amount: -50} ]; travellers: [ {guild: "beggar"} ]; base of 0[0] — 8 plus -50 is negative, and a toll is never below zero
- reward
- 510 XP
- signature
tollFor
TypeScript, run as ES2022 — types are stripped, not checked. If a JS engine would run it, so will we.
- an empty toll-book charges the base
- specificity first, then the later page
- a pass yields only to a strictly finer toll
- extras stack, and nothing is charged below nothing
- extras ride on the base when no toll rule matches
- nobody on the bridge