Urbit Docs
  • What is Urbit?
  • Get on Urbit
  • Build on Urbit
    • Contents
    • Environment Setup
    • Hoon School
      • 1. Hoon Syntax
      • 2. Azimuth (Urbit ID)
      • 3. Gates (Functions)
      • 4. Molds (Types)
      • 5. Cores
      • 6. Trees and Addressing
      • 7. Libraries
      • 8. Testing Code
      • 9. Text Processing I
      • 10. Cores and Doors
      • 11. Data Structures
      • 12. Type Checking
      • 13. Conditional Logic
      • 14. Subject-Oriented Programming
      • 15. Text Processing II
      • 16. Functional Programming
      • 17. Text Processing III
      • 18. Generic and Variant Cores
      • 19. Mathematics
    • App School I
      • 1. Arvo
      • 2. The Agent Core
      • 3. Imports and Aliases
      • 4. Lifecycle
      • 5. Cards
      • 6. Pokes
      • 7. Structures and Marks
      • 8. Subscriptions
      • 9. Vanes
      • 10. Scries
      • 11. Failure
      • 12. Next Steps
      • Appendix: Types
    • App School II (Full-Stack)
      • 1. Types
      • 2. Agent
      • 3. JSON
      • 4. Marks
      • 5. Eyre
      • 6. React app setup
      • 7. React app logic
      • 8. Desk and glob
      • 9. Summary
    • Core Academy
      • 1. Evaluating Nock
      • 2. Building Hoon
      • 3. The Core Stack
      • 4. Arvo I: The Main Sequence
      • 5. Arvo II: The Boot Sequence
      • 6. Vere I: u3 and the Serf
      • 7. Vere II: The Loom
      • 8. Vanes I: Behn, Dill, Kahn, Lick
      • 9. Vanes II: Ames
      • 10. Vanes III: Eyre, Iris
      • 11. Vanes IV: Clay
      • 12. Vanes V: Gall and Userspace
      • 13. Vanes VI: Khan, Lick
      • 14. Vanes VII: Jael, Azimuth
    • Runtime
      • U3
      • Conn.c Guide
      • How to Write a Jet
      • API Overview by Prefix
      • C in Urbit
      • Cryptography
      • Land of Nouns
    • Tools
      • Useful Links
      • JS Libraries
        • HTTP API
      • Docs App
        • File Format
        • Index File
        • Suggested Structure
    • Userspace
      • Command-Line App Tutorial
      • Remote Scry
      • Unit Tests
      • Software Distribution
        • Software Distribution Guide
        • Docket File
        • Glob
      • Examples
        • Building a CLI App
        • Debugging Wrapper
        • Host a Website
        • Serving a JS Game
        • Ship Monitoring
        • Styled Text
  • Urbit ID
    • What is Urbit ID?
    • Azimuth Data Flow
    • Life and Rift
    • Urbit HD Wallet
    • Advanced Azimuth Tools
    • Custom Roller Tutorial
    • Azimuth.eth Reference
    • Ecliptic.eth Reference
    • Layer 2
      • L2 Actions
      • L2 Rollers
      • L2 Roller HTTP RPC-API
      • L2 Transaction Format
  • Urbit OS
    • What is Urbit OS?
    • Base
      • Hood
      • Threads
        • Basics Tutorial
          • Bind
          • Fundamentals
          • Input
          • Output
          • Summary
        • HTTP API Guide
        • Spider API Reference
        • Strandio Reference
        • Examples
          • Child Thread
          • Fetch JSON
          • Gall
            • Poke Thread
            • Start Thread
            • Stop Thread
            • Take Facts
            • Take Result
          • Main-loop
          • Poke Agent
          • Scry
          • Take Fact
    • Kernel
      • Arvo
        • Cryptography
        • Move Trace
        • Scries
        • Subscriptions
      • Ames
        • Ames API Reference
        • Ames Cryptography
        • Ames Data Types
        • Ames Scry Reference
      • Behn
        • Behn API Reference
        • Behn Examples
        • Behn Scry Reference
      • Clay
        • Clay API Reference
        • Clay Architecture
        • Clay Data Types
        • Clay Examples
        • Clay Scry Reference
        • Filesystem Hierarchy
        • Marks
          • Mark Examples
          • Using Marks
          • Writing Marks
        • Using Clay
      • Dill
        • Dill API Reference
        • Dill Data Types
        • Dill Scry Reference
      • Eyre
        • EAuth
        • Eyre Data Types
        • Eyre External API
        • Eyre Internal API
        • Eyre Scry Reference
        • Low-Level Eyre Guide
        • Noun channels
      • Gall
        • Gall API Reference
        • Gall Data Types
        • Gall Scry Reference
      • Iris
        • Iris API Reference
        • Iris Data Types
        • Iris Example
      • Jael
        • Jael API Reference
        • Jael Data Types
        • Jael Examples
        • Jael Scry Reference
      • Khan
        • Khan API Reference
        • Khan Data Types
        • Khan Example
      • Lick
        • Lick API Reference
        • Lick Guide
        • Lick Examples
        • Lick Scry Reference
  • Hoon
    • Why Hoon?
    • Advanced Types
    • Arvo
    • Auras
    • Basic Types
    • Cheat Sheet
    • Cryptography
    • Examples
      • ABC Blocks
      • Competitive Programming
      • Emirp
      • Gleichniszahlenreihe
      • Islands
      • Luhn Number
      • Minimum Path Sum
      • Phone Letters
      • Restore IP
      • Rhonda Numbers
      • Roman Numerals
      • Solitaire Cipher
      • Water Towers
    • Generators
    • Hoon Errors
    • Hoon Style Guide
    • Implementing an Aura
    • Irregular forms
    • JSON
    • Limbs and wings
      • Limbs
      • Wings
    • Mips (Maps of Maps)
    • Parsing Text
    • Runes
      • | bar · Cores
      • $ buc · Structures
      • % cen · Calls
      • : col · Cells
      • . dot · Nock
      • / fas · Imports
      • ^ ket · Casts
      • + lus · Arms
      • ; mic · Make
      • ~ sig · Hints
      • = tis · Subject
      • ? wut · Conditionals
      • ! zap · Wild
      • Constants (Atoms and Strings)
      • --, == · Terminators
    • Sail (HTML)
    • Serialization
    • Sets
    • Standard Library
      • 1a: Basic Arithmetic
      • 1b: Tree Addressing
      • 1c: Molds and Mold-Builders
      • 2a: Unit Logic
      • 2b: List Logic
      • 2c: Bit Arithmetic
      • 2d: Bit Logic
      • 2e: Insecure Hashing
      • 2f: Noun Ordering
      • 2g: Unsigned Powers
      • 2h: Set Logic
      • 2i: Map Logic
      • 2j: Jar and Jug Logic
      • 2k: Queue Logic
      • 2l: Container from Container
      • 2m: Container from Noun
      • 2n: Functional Hacks
      • 2o: Normalizing Containers
      • 2p: Serialization
      • 2q: Molds and Mold-Builders
      • 3a: Modular and Signed Ints
      • 3b: Floating Point
      • 3c: Urbit Time
      • 3d: SHA Hash Family
      • 3e: AES encryption (Removed)
      • 3f: Scrambling
      • 3g: Molds and Mold-Builders
      • 4a: Exotic Bases
      • 4b: Text Processing
      • 4c: Tank Printer
      • 4d: Parsing (Tracing)
      • 4e: Parsing (Combinators)
      • 4f: Parsing (Rule-Builders)
      • 4g: Parsing (Outside Caller)
      • 4h: Parsing (ASCII Glyphs)
      • 4i: Parsing (Useful Idioms)
      • 4j: Parsing (Bases and Base Digits)
      • 4k: Atom Printing
      • 4l: Atom Parsing
      • 4m: Formatting Functions
      • 4n: Virtualization
      • 4o: Molds
      • 5a: Compiler Utilities
      • 5b: Macro Expansion
      • 5c: Compiler Backend & Prettyprinter
      • 5d: Parser
      • 5e: Molds and mold builders
      • 5f: Profiling support
    • Strings
    • The Engine Pattern
    • Udon (Markdown-esque)
    • Vases
    • Zuse
      • 2d(1-5): To JSON, Wains
      • 2d(6): From JSON
      • 2d(7): From JSON (unit)
      • 2e(2-3): Print & Parse JSON
      • 2m: Ordered Maps
  • Nock
    • What is Nock?
    • Decrement
    • Definition
    • Fast Hints and Jets
    • Implementations
    • Specification
  • User Manual
    • Contents
    • Running Urbit
      • Cloud Hosting
      • Home Servers
      • Runtime Reference
      • Self-hosting S3 Storage with MinIO
    • Urbit ID
      • Bridge Troubleshooting
      • Creating an Invite Pool
      • Get an Urbit ID
      • Guide to Factory Resets
      • HD Wallet (Master Ticket)
      • Layer 2 for planets
      • Layer 2 for stars
      • Proxies
      • Using Bridge
    • Urbit OS
      • Basics
      • Configuring S3 Storage
      • Dojo Tools
      • Filesystem
      • Shell
      • Ship Troubleshooting
      • Star and Galaxy Operations
      • Updates
Powered by GitBook

GitHub

  • Urbit ID
  • Urbit OS
  • Runtime

Resources

  • YouTube
  • Whitepaper
  • Awesome Urbit

Contact

  • X
  • Email
  • Gather
On this page
  • Challenge: Printing and Parsing Roman Numerals
  • Unit Tests
  • Solutions
  • Solution #1
  • Solution #2
  • Solution #3
  • Solution #4
Edit on GitHub
  1. Hoon
  2. Examples

Roman Numerals

Challenge: Printing and Parsing Roman Numerals

Roman numerals constitute a numeral system capable of expressing positive integers by additive values (rather than place-number notation). Additive series are produced by summing values in a series, as iii → 3, while subtractive values are produced by prepending certain smaller values ahead of a larger value, as ix → 9.

  • Produce a library which converts to and from Roman numeral representations according to the standard values:

    Character
    Value

    i

    1

    v

    5

    x

    10

    l

    50

    c

    100

    d

    500

    m

    1,000

    There are many incorrect formulations, as iix → 8 or id → 499, and the code is not expected to parse these “correctly”. (It should not produce them!) However, both iv and iiii are frequently used to represent 4 (e.g. look at a clock face), so you should support this variation.

    For this task, produce two files:

    • /lib/roman/hoon

      Your library /lib/roman/hoon should expose two arms:

      • ++parse accepts a tape text string containing a Roman numeral expression in lower or upper case and returns the corresponding @ud unsigned decimal value. On failure to parse, call !! zapzap.

      • ++yield accepts a @ud unsigned decimal value and returns the corresponding tape text string in lower case.

    • /gen/roman/hoon

      Provide a %say generator at /gen/roman/hoon which accepts a tape text string or a @ud unsigned decimal value and performs the appropriate conversion on the basis of the sample's type.

      Note: This design pattern is not optimal since analysis over a union of some types can be difficult to carry out, and it would be better to either separate the generators or use a flag. In this case, the pattern works because we are distinguishing an atom from a cell.

Unit Tests

Following a principle of test-driven development, we compose a series of tests which allow us to rigorously check for expected behavior.

/+  *test, *roman
|%
++  test-output-one
  =/  src  "i"
  =/  trg  1
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-two
  =/  src  "ii"
  =/  trg  2
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-three
  =/  src  "iii"
  =/  trg  3
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-four
  =/  src  "iv"
  =/  trg  4
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-four-var
  =/  src  "iiii"
  =/  trg  4
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-five
  =/  src  "v"
  =/  trg  5
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-six
  =/  src  "vi"
  =/  trg  6
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-seven
  =/  src  "vii"
  =/  trg  7
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-eight
  =/  src  "viii"
  =/  trg  8
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-nine
  =/  src  "ix"
  =/  trg  9
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-ten
  =/  src  "x"
  =/  trg  10
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-eleven
  =/  src  "xi"
  =/  trg  11
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-twelve
  =/  src  "xii"
  =/  trg  12
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-thirteen
  =/  src  "xiii"
  =/  trg  13
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-fourteen
  =/  src  "xiv"
  =/  trg  14
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-fifteen
  =/  src  "xv"
  =/  trg  15
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-sixteen
  =/  src  "xvi"
  =/  trg  16
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-seventeen
  =/  src  "xvii"
  =/  trg  17
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-eighteen
  =/  src  "xviii"
  =/  trg  18
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-nineteen
  =/  src  "xix"
  =/  trg  19
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-twenty
  =/  src  "xx"
  =/  trg  20
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-twenty-three
  =/  src  "xxiii"
  =/  trg  23
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-twenty-five
  =/  src  "xxv"
  =/  trg  25
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-twenty-seven
  =/  src  "xxvii"
  =/  trg  27
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-thirty-one
  =/  src  "xxxi"
  =/  trg  31
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-thirty-nine
  =/  src  "xxxix"
  =/  trg  39
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-forty-two
  =/  src  "xlii"
  =/  trg  42
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-forty-nine
  =/  src  "xlix"
  =/  trg  49
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-fifty
  =/  src  "l"
  =/  trg  50
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-sixty-two
  =/  src  "lxii"
  =/  trg  62
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-seventy-eight
  =/  src  "lxxviii"
  =/  trg  78
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-ninety-four-var
  =/  src  "xciiii"
  =/  trg  94
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-one-hundred
  =/  src  "c"
  =/  trg  100
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-one-hundred-thirty-three
  =/  src  "cxxxiii"
  =/  trg  133
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-four-hundred-ninety-nine
  =/  src  "cdxcix"
  =/  trg  499
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-five-hundred
  =/  src  "d"
  =/  trg  500
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-five-hundred-forty-eight
  =/  src  "dxlviii"
  =/  trg  548
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-six-hundred-sixty-nine
  =/  src  "dclxix"
  =/  trg  669
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-eight-hundred-eighty-eight
  =/  src  "dccclxxxviii"
  =/  trg  888
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-nine-hundred-ninety-nine
  =/  src  "cmxcix"
  =/  trg  999
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-one-thousand
  =/  src  "m"
  =/  trg  1.000
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-one-thousand-nine-hundred-ninety-nine
  =/  src  "mcmxcix"
  =/  trg  1.999
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-two-thousand-twenty-two
  =/  src  "mmxxii"
  =/  trg  2.022
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-output-three-thousand-nine-hundred-ninety-nine
  =/  src  "mmmcmxcix"
  =/  trg  3.999
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (parse src)
  %+  expect-eq
    !>  trg
    !>  (parse (cuss src))
  ==
++  test-input-one
  =/  trg  "i"
  =/  src  1
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-two
  =/  trg  "ii"
  =/  src  2
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-three
  =/  trg  "iii"
  =/  src  3
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-four
  =/  trg  "iv"
  =/  src  4
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-five
  =/  trg  "v"
  =/  src  5
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-six
  =/  trg  "vi"
  =/  src  6
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-seven
  =/  trg  "vii"
  =/  src  7
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-eight
  =/  trg  "viii"
  =/  src  8
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-nine
  =/  trg  "ix"
  =/  src  9
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-ten
  =/  trg  "x"
  =/  src  10
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-eleven
  =/  trg  "xi"
  =/  src  11
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-twelve
  =/  trg  "xii"
  =/  src  12
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-thirteen
  =/  trg  "xiii"
  =/  src  13
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-fourteen
  =/  trg  "xiv"
  =/  src  14
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-fifteen
  =/  trg  "xv"
  =/  src  15
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-sixteen
  =/  trg  "xvi"
  =/  src  16
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-seventeen
  =/  trg  "xvii"
  =/  src  17
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-eighteen
  =/  trg  "xviii"
  =/  src  18
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-nineteen
  =/  trg  "xix"
  =/  src  19
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-twenty
  =/  trg  "xx"
  =/  src  20
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-twenty-three
  =/  trg  "xxiii"
  =/  src  23
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-twenty-five
  =/  trg  "xxv"
  =/  src  25
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-twenty-seven
  =/  trg  "xxvii"
  =/  src  27
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-thirty-one
  =/  trg  "xxxi"
  =/  src  31
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-thirty-nine
  =/  trg  "xxxix"
  =/  src  39
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-forty-two
  =/  trg  "xlii"
  =/  src  42
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-forty-nine
  =/  trg  "xlix"
  =/  src  49
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-fifty
  =/  trg  "l"
  =/  src  50
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-sixty-two
  =/  trg  "lxii"
  =/  src  62
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-seventy-eight
  =/  trg  "lxxviii"
  =/  src  78
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-one-hundred
  =/  trg  "c"
  =/  src  100
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-one-hundred-thirty-three
  =/  trg  "cxxxiii"
  =/  src  133
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-four-hundred-ninety-nine
  =/  trg  "cdxcix"
  =/  src  499
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-five-hundred
  =/  trg  "d"
  =/  src  500
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-five-hundred-forty-eight
  =/  trg  "dxlviii"
  =/  src  548
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-six-hundred-sixty-nine
  =/  trg  "dclxix"
  =/  src  669
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-eight-hundred-eighty-eight
  =/  trg  "dccclxxxviii"
  =/  src  888
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-nine-hundred-ninety-nine
  =/  trg  "cmxcix"
  =/  src  999
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-one-thousand
  =/  trg  "m"
  =/  src  1.000
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-one-thousand-nine-hundred-ninety-nine
  =/  trg  "mcmxcix"
  =/  src  1.999
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-two-thousand-twenty-two
  =/  trg  "mmxxii"
  =/  src  2.022
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
++  test-input-three-thousand-nine-hundred-ninety-nine
  =/  trg  "mmmcmxcix"
  =/  src  3.999
  ;:  weld
  %+  expect-eq
    !>  trg
    !>  (yield src)
  ==
--

Solutions

These solutions were submitted by the Urbit community as part of a competition in ~2022.6. They are made available under both the MIT license and the CC0 license. We ask you to acknowledge authorship should you utilize these elsewhere.

Solution #1

This solution was produced by ~sidnym-ladrut. This code utilizes the Hoon parser tools like ++cook and ++scan, and in particular illustrates a strong ethic of function encapsulation.

/lib/roman.hoon

::  roman: roman numeral conversion library
::
=<
::  public core
|%
::  +parse: given a roman numeral, produce the equivalent arabic numeral
::
++  parse
  |=  roman=tape
  ^-  @ud
  ~|  'Input numeral has invalid syntax.'
  ?>  !=((lent roman) 0)
  |^  %+  scan  (cass roman)
      %+  cook  sum-up
      ;~  plug
        (parse-just (pow 10 3) 0 3)
        (parse-base (pow 10 2) 3)
        (parse-base (pow 10 1) 3)
        (parse-base (pow 10 0) 4)
        (easy ~)
      ==
  ::  +sum-up: sum up the contents of a given list
  ::
  ++  sum-up
    |=  l=(list @)
    (roll l add)
  ::  +parse-just: parse just the roman equivalent of given arabic [range] times
  ::
  ++  parse-just
    |=  [value=@ud range=[@ud @ud]]
    %+  cook  sum-up
    %+  stun  range
    %+  cold  value
    (jest (~(got by glyph-map) value))
  ::  +parse-base: parse the roman base of given base-10 arabic [0, reps] times
  ::
  ::    This function parses the *contextualized* roman base equivalent of a
  ::    given base-10 arabic value up to a given number of times. Crucially,
  ::    this applies roman numeral contextual rules, such as numeral ordering
  ::    and subtraction rules (e.g. iv=4, ix=9, id=invalid, etc.), to the given
  ::    base. In concrete terms, this means enforcing the following regex:
  ::
  ::    ```
  ::    R: Reps | N: Next (B*10)
  ::    B: Base | H: Half (B*5)
  ::
  ::    (BN|BH|H?B{0,R})
  ::    ```
  ::
  ++  parse-base
    |=  [base=@ud reps=@ud]
    =+  next=(mul base 10)
    =+  half=(mul base 5)
    ;~  pose
      (parse-just (sub next base) 1 1)
      (parse-just (sub half base) 1 1)
      %+  cook  sum-up
      ;~  plug
        (parse-just half 0 1)
        (parse-just base 0 reps)
        (easy ~)
      ==
      (easy 0)
    ==
  --
::  +yield: given an arabic numeral, produce the equivalent roman numeral
::
++  yield
  |=  arabic=@ud
  ^-  tape
  ~|  'Input value is out of range (valid range: [1, 3.999]).'
  ?>  &((gth arabic 0) (lth arabic 4.000))
  =<  +>
  %^  spin  glyph-list  [arabic ""]
  |=  [n=[@ud @t] a=[@ud tape]]
  ?:  (lth -.a -.n)  [n a]
  $(a [(sub -.a -.n) (weld +.a (trip +.n))])
--
::  private core
|%
::  +glyph-map: map of arabic glyphs to their roman equivalents
::
++  glyph-map
  ^-  (map @ud @t)
  (malt glyph-list)
::  +glyph-list: list of pairs of equivalent [arabic roman] glyphs
::
++  glyph-list
  ^-  (list [@ud @t])
  :~  :-  1.000   'm'
      :-  900    'cm'
      :-  500     'd'
      :-  400    'cd'
      :-  100     'c'
      :-  90     'xc'
      :-  50      'l'
      :-  40     'xl'
      :-  10      'x'
      :-  9      'ix'
      :-  5       'v'
      :-  4      'iv'
      :-  1       'i'
  ==
--

/gen/roman.hoon

::  +roman: given arabic or roman numeral, produce the opposite
::
::    +roman @ud
::      given arabic numeral, generate roman equivalent
::    +roman tape
::      given roman numeral, generate arabic equivalent
::
/+  *roman
::
:-  %say
|=  [* [i=?(@ud tape) ~] ~]
:-  %noun
^-  ?(@ud tape)
?-  i
  ~   (parse i)
  @   (yield i)
  ^   (parse i)
==

Solution #2

This solution was produced by ~mocmex-pollen. It particularly illustrates the use of ++cook and ++pose in constructing a parser-based solution.

/lib/roman.hoon

::
::  A library for parsing and producing Roman numeral expressions.
::
=<
::
|%
::  +parse: produce the value of a Roman numeral expression
::
++  parse
  |=  expression=tape
  ^-  @ud
  %+  scan
    (cass expression)
  %-  full
  ;~  (comp |=([a=@ud b=@ud] (add a b)))
    (cook roman-value-unit (punt (numeral-rule %m)))
    (cook roman-value-unit (punt (numeral-rule %d)))
    (cook roman-value-unit (punt (numeral-rule %c)))
    (cook roman-value-unit (punt (numeral-rule %l)))
    (cook roman-value-unit (punt (numeral-rule %x)))
    (cook roman-value-unit (punt (numeral-rule %v)))
    (cook roman-value-unit (punt (numeral-rule %i)))
  ==
::  +yield: produce the Roman numeral for a given value
::
++  yield
  |=  n=@ud
  ^-  tape
  ?>  (gte n 1)
  ::
  =/  options  numerals-and-subtractives
  =/  final  *tape
  |-
  ?:  =(n 0)
    final
  ?~  options
    !!
  =/  roman=tape  -.i.options
  =/  value=@ud  +.i.options
  =/  expression=tape  (zing (reap (div n value) roman))
  %=  $
    n        (mod n value)
    options  t.options
    final    (weld final expression)
  ==
--
::
|%  
::  +numeral-rule: match valid sequences that begin with the given numeral
::
++  numeral-rule
  |=  numeral=?(%i %v %x %l %c %d %m)
  ?-  numeral
    %i  ;~(pose (jest 'iiii') (jest 'iii') (jest 'ii') (jest 'iv') (jest 'ix') (just 'i'))
    %v  (just 'v')
    %x  ;~(pose (jest 'xxx') (jest 'xx') (jest 'xc') (jest 'xl') (just 'x'))
    %l  (just 'l')
    %c  ;~(pose (jest 'ccc') (jest 'cc') (jest 'cm') (jest 'cd') (just 'c'))
    %d  (just 'd')
    %m  ;~(pose (jest 'mmm') (jest 'mm') (just 'm'))
  ==
::  +roman-value-unit: 0 if the unit is empty otherwise ++roman-value
::
++  roman-value-unit
  |=  roman=(unit @t)
  ^-  @ud
  ?~  roman
    0
  (roman-value (trip u.roman))
::  +roman-value: produce the value of a simple expression
::
::    "Simple" here means a single numeral, an additive series or 
::    a subtractive pair. ex: "i", "ii", "iv" but not "xi"
:: 
::    Caution: this will produce a value for an invalid Roman numeral and
::    a wrong value for "complex" expressions.
::
++  roman-value
  |=  roman=tape
  ^-  @ud
  ?~  roman
    !!
  ::
  =/  value-map  (malt numerals-and-subtractives)
  %+  fall
    ::  roman is a single numeral or a subtractive
    ::
    (~(get by value-map) roman)
  ::  roman is an additive series
  ::
  %+  mul
    (lent roman)
  (~(got by value-map) (trip i.roman))
::  +numerals-and-subtractives: a list of pairs of single numerals 
::  and valid subtractive pairs in descending order of value
::
++  numerals-and-subtractives
  ^-  (list [tape @ud])
  :~  ["m" 1.000]
      ["cm" 900]
      ["d" 500]
      ["cd" 400]
      ["c" 100]
      ["xc" 90]
      ["l" 50]
      ["xl" 40]
      ["x" 10]
      ["ix" 9]
      ["v" 5]
      ["iv" 4]
      ["i" 1]
  ==
--

/gen/roman.hoon

::
::  %say the product of the conversion to/from a Roman numeral expression
::
::    The direction of conversion is determined by the type of the input.
::    tape -> Roman numeral expression to a quantity
::    @ud -> quantity to a Roman numeral expression
::
/+  *roman
::
:-  %say
::  caution - the type union in this spec is sensitive to the order of
::  its arguments: ?(tape @ud) results in fish-loop
::
|=  [* [value=?(@ud tape) ~] *]
^-  [%noun ?(@ud tape)]
:-  %noun
?:  ?=(@ud value)
  (yield value)
(parse value)

Solution #3

This solution was produced by ~mashex-masrex. Notice how it utilizes a well-structured parser based on ++jest and ++cold.

/lib/roman.hoon

::  Convert Roman numerals to Arabic numbers, or vice versa.
::
|%
::  +parse: accept a tape containing a roman numeral and produce the number
::
++  parse
  |=  numeral=tape  ^-  @ud
  ::  (the sum of arabic numbers that are found in the roman numeral)
  ::
  |^  (roll (scan (cuss numeral) (star as-arabic)) add)
  ::  +as-arabic: convert numeral characters into their numeric value
  ::
  ++  as-arabic
    ;~  pose
      (cold 4 (jest 'IV'))
      (cold 9 (jest 'IX'))
      (cold 1 (just 'I'))
      (cold 5 (just 'V'))
      (cold 40 (jest 'XL'))
      (cold 90 (jest 'XC'))
      (cold 10 (just 'X'))
      (cold 50 (just 'L'))
      (cold 400 (jest 'CD'))
      (cold 900 (jest 'CM'))
      (cold 100 (just 'C'))
      (cold 500 (just 'D'))
      (cold 1.000 (just 'M'))
    ==
  --
::  +yield: accept a decimal number and produce the corresponding roman numeral
::
++  yield
  |=  number=@ud  ^-  tape
  :: if, number is zero
  ::
  ?:  =(0 number)
    ::  then, end the list (i.e. conclude the tape)
    ::
    ~
  ::  else, if, number is one-thousand or greater
  ::
  ?:  (gte number 1.000)
    ::  then, append "m", and recurse subtracting one-thousand
    ::
    :-  'm'
    $(number (sub number 1.000))
  ::  else, if, number is nine-hundred or greater
  ::
  ?:  (gte number 900)
    ::  then, append "cm", and recurse subtracting nine-hundred
    ::
    :-  'c'  :-  'm'
    $(number (sub number 900))
  ::  else, if, number is five-hundred or greater
  ::
  ?:  (gte number 500)
    ::  then, append "d", and recurse subtracting five-hundred
    ::
    :-  'd'
    $(number (sub number 500))
  ::  else, if, number is four-hundred or greater
  ::
  ?:  (gte number 400)
    ::  then, append "cd", and recurse subtracting four-hundred
    ::
    :-  'c'  :-  'd'
    $(number (sub number 400))
  ::  else, if, number is one-hundred or greater
  ::
  ?:  (gte number 100)
    ::  then, append "c", and recurse subtracting one-hundred
    ::
    :-  'c'
    $(number (sub number 100))
  ::  else, if, number is ninety or greater
  ::
  ?:  (gte number 90)
    ::  then, append "xc", and recurse subtracting ninety
    ::
    :-  'x'  :-  'c'
    $(number (sub number 90))
  ::  else, if, number is fifty or greater
  ::
  ?:  (gte number 50)
    ::  then, append "l", and recurse subtracting fifty
    ::
    :-  'l'
    $(number (sub number 50))
  ::  else, if, number is forty or greater
  ::
  ?:  (gte number 40)
    ::  then, append "xl", and recurse subtracting forty
    ::
    :-  'x'  :-  'l'
    $(number (sub number 40))
  ::  else, if, number is ten or greater
  ::
  ?:  (gte number 10)
    ::  then, append "x", and recurse subtracting ten
    ::
    :-  'x'
    $(number (sub number 10))
  ::  else, if, number is nine or greater
  ::
  ?:  (gte number 9)
    ::  then, append "ix", and recurse subtracting nine
    ::
    :-  'i'  :-  'x'
    $(number (sub number 9))
  ::  else, if, number is five or greater
  ::
  ?:  (gte number 5)
    ::  then, append "v", and recurse subtracting five
    ::
    :-  'v'
    $(number (sub number 5))
  ::  else, if, number is four or greater
  ::
  ?:  (gte number 4)
    ::  then, append "iv", and recurse subtracting four
    ::
    :-  'i'  :-  'v'
    $(number (sub number 4))
  ::  else, append "i", and recurse subtracting one
  ::
  :-('i' $(number (sub number 1)))
--

/gen/roman.hoon

::  roman: Convert Roman numerals to Arabic numbers, or vice versa.
::
/+  *roman
:-  %say
|=  [* [arabic-or-roman=$@(@ud tape) ~] ~]
:-  %noun
::  if, arabic-or-roman is null
::
?~  arabic-or-roman
  ::  then, produce zero
  0
::  else, if, arabic-or-roman is a cell
::
?^  arabic-or-roman
  ::  then, parse the tape of roman numerals
  ::
  (parse arabic-or-roman)
::  else, produce a roman numeral from the arabic number
::
(yield arabic-or-roman)

Solution #4

This solution was produced by ~fonnyx-nopmer. It comes sans comments, and particularly demonstrates how to produce legible and idiomatic Hoon code without requiring comments.

/lib/roman.hoon

|%
++  parse
  |=  t=tape  ^-  @ud
  =.  t  (cass t)
  =|  result=@ud
  |-
  ?~  t  result
  ?~  t.t  (add result (from-numeral i.t))
  =+  [a=(from-numeral i.t) b=(from-numeral i.t.t)]
  ?:  (gte a b)  $(result (add result a), t t.t)
  $(result (sub (add result b) a), t t.t.t)
++  yield
  |=  n=@ud  ^-  tape
  =|  result=tape
  =/  values  to-numeral
  |-
  ?~  values  result
  ?:  (gte n -.i.values)
    $(result (weld result +.i.values), n (sub n -.i.values))
  $(values t.values)
++  from-numeral
  |=  c=@t  ^-  @ud
  ?:  =(c 'i')  1
  ?:  =(c 'v')  5
  ?:  =(c 'x')  10
  ?:  =(c 'l')  50
  ?:  =(c 'c')  100
  ?:  =(c 'd')  500
  ?:  =(c 'm')  1.000
  !!
++  to-numeral
  ^-  (list [@ud tape])
  :*
    [1.000 "m"]
    [900 "cm"]
    [500 "d"]
    [400 "cd"]
    [100 "c"]
    [90 "xc"]
    [50 "l"]
    [40 "xl"]
    [10 "x"]
    [9 "ix"]
    [5 "v"]
    [4 "iv"]
    [1 "i"]
    ~
  ==
--

/gen/roman.hoon

/+  *roman
:-  %say
|=  [* [x=$%([%from-roman tape] [%to-roman @ud]) ~] ~]
:-  %noun
^-  tape
?-  -.x
  %from-roman  "{<(parse +.x)>}"
  %to-roman  (yield +.x)
==
PreviousRhonda NumbersNextSolitaire Cipher

Last updated 1 day ago