The challenges were largely based on the [Rosetta Code](https://rosettacode.org/) code illustration examples, enabling us to give more exemplary instances of Hoon code to the broader development community.
Two prizes were recognized for each: one for the first over the finish line, and another for the most stylistically expressive Hoon, as judged blindly by Hoon School Live's graders, ~tinnus-napbus, ~hodzod-walrus, and ~dinleb-rambep.
The remainder of this blog post [first introduces the Hoon Workbook](#the-hoon-workbook) and then [summarizes the competition results](#the-results).
## The Hoon Workbook
We are also using the opportunity of this competition to reboot the [Hoon Workbook](/guides/additional/workbook). The original [Hoon workbook](https://github.com/urbit/hoon-workbook/) was a showcase of material that had at one time been in the documentation but didn't stylistically flow with docs demands at a later date, so was removed but kept as still valuable. We are going to treat the Hoon Workbook as a set of standalone tutorials which illustrate useful principles of code architecture and design, including some light tutorial-style commentary. We will expand it from time to time and link it from Hoon School and App School as appropriate.
The first two pages in the Hoon Workbook are comparison code constructions selected from the submissions to the HSL Competition. We commend them to your study:
One of the signal advantages of watching a code competition is laterally discovering ways of thinking that other coders employ. As you examine the top code submissions, consider the relative variety of approaches (e.g. for Rhonda numbers) and the high degree of similarity apparent for others (e.g. for Roman numeral production).
### Rhonda Numbers
A Rhonda number is a positive integer _n_ that satisfies the property that, for [a given base _b_](https://en.wikipedia.org/wiki/Radix), the product of the base-_b_ digits of _n_ is equal to _b_ times the sum of _n_'s prime factors. Only composite bases (non-prime bases) have Rhonda numbers.
For instance, consider 10206₁₀ = 2133132₄, that is, 2×4⁶ + 1×4⁵ + 3×4⁴ + 3×4³ + 1×4² + 3×4¹ + 2×4⁰ = 2×4096₁₀ + 1×1024₁₀ + 3×256₁₀ + 3×64₁₀ + 1×16₁₀ + 3×4₁₀ + 2 = 8192₁₀ + 1024₁₀ + 768₁₀ + 192₁₀ + 16₁₀ + 12₁₀ + 2 = 10206₁₀. 10206₁₀ has the prime factorization (2, 3, 3, 3, 3, 3, 3, 7) because 2×3⁶×7 = 10206₁₀. This is a base-4 Rhonda number because 2×1×3×3×1×3×2 = 108₁₀ and 4×(2+3+3+3+3+3+3+7) = 4×27₁₀ = 108₁₀.
For this task, you will produce three files:
-`/lib/rhonda/hoon`
Your library `/lib/rhonda/hoon` should expose two arms:
-`++check` accepts a `@ud` unsigned decimal value for the base and a `@ud` unsigned decimal value for the number, and returns `%.y` or `%.n` depending on whether the given number is a Rhonda number in that base or not.
-`++series` accepts a base as a `@ud` unsigned decimal value and a number of values to return `n`, and either returns `~` if the base is prime or the `n` first Rhonda numbers in that base.
-`/gen/rhonda-check/hoon`
You should provide a `%say` generator at `/gen/rhonda-check/hoon` which accepts a `@ud` unsigned decimal value and applies `++check` to verify if that value is a Rhonda number or not.
-`/gen/rhonda-series/hoon`
You should provide a `%say` generator at `/gen/rhonda-series/hoon` which accepts a `@ud` unsigned decimal value `b` and a `@ud` unsigned decimal value `n`, where `b` is the base _b_, and returns the first _n_ Rhonda numbers in that base.
:: "Rhonda numbers exist only for bases that are composite since
:: there is no way for the product of integers less than a prime b
:: to have b as a factor."
?: (is-prime b)
%.n
=+ baseb=(as-base b n)
=+ facts=(prime-factors n)
.= (roll baseb mul)
(mul b (roll facts add))
++ series :: list first n rhondas of base b
|= [b=@ud n=@ud]
^- (list @ud)
=| l=(list @ud)
=/ i=@ud 2
=/ c=@ud 0
?: (is-prime b)
l
|-
?: =(c n)
(flop l)
?. (check b i)
$(i +(i))
$(i +(i), c +(c), l [i l])
```
### 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.
You should 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 your 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, you will 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`
You should also 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.
:: +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)))
--
```
~fonnyx-nopmer
```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"]
~
==
--
```
### Alignment and Titling
This challenge will have you implement tooling to left-align, center, and right-align a given tape, and to draw various kinds of boxes around the text.
Given text and an overall width, you should left-align, center, or right-align the text:
```hoon
> (align %left 80 "Welcome to Mars!")
"Welcome to Mars! "
> (align %right 60 "Welcome to Mars!")
" Welcome to Mars!"
> (align %center 40 "Welcome to Mars!")
" Welcome to Mars! "
```
In case of misalignment on odd/even lengths, move one to the left:
```hoon
> (align %center 16 "cat")
" cat "
```
To make boxes, you should produce a block of box-drawing characters at the specified distance from the text. E.g. `(box-text 0 "Welcome to Mars")` should produce a [`wall`](https://urbit.org/docs/hoon/reference/stdlib/2q#wall) (`list tape`) containing the text tightly bound in a box:
```
┌────────────────┐
│Welcome to Mars!│
└────────────────┘
```
or `~["┌────────────────┐" "│Welcome to Mars!│" "└────────────────┘"]`
whereas `(box-text 1 "Welcome to Mars")` should produce a `tape` containing the text with one unit of breathing space around it:
```
┌──────────────────┐
│ │
│ Welcome to Mars! │
│ │
└──────────────────┘
```
`++double-box-text` should do the same but using the `═` series.
Finally, there should also be a generic symbol boxer:
```hoon
> (symbol-box '*' 1 "Welcome to Mars!")
********************
* *
* Welcome to Mars! *
* *
********************
> (symbol-box '*' 1 (align %center 24 "Welcome to Mars!"))
****************************
* *
* Welcome to Mars! *
* *
****************************
```
Solutions will benefit if you spend some time looking at the available text processors and printers defined in the standard library.
Our other task is to take a long block of text and break it into a number of columns. Given a text file of many lines, where fields within a line are delineated by a single `$` buc character, write a program that aligns each column of fields by ensuring that words in each column are separated by at least one space. Further, allow for each word in a column to be either left justified, right justified, or center justified within its column.
~["Given a txt file of many lines, where fields within a line"
"are delineated by a single 'dollar' character, write a program "
"that aligns each column of fields by ensuring that words in each"
"column are separated by at least one space. "
"Further, allow for each word in a column to be either left"
"justified, right justified, or center justified within its column. "]
> (columnify %center sample)
~[" Given a txt file of many lines, where fields within a line"
" are delineated by a single 'dollar' character, write a program "
" that aligns each column of fields by ensuring that words in each"
" column are separated by at least one space. "
" Further, allow for each word in a column to be either left"
"justified, right justified, or center justified within its column."]
> (columnify %right sample)
~[" Given a txt file of many lines, where fields within a line"
" are delineated by a single 'dollar' character, write a program "
" that aligns each column of fields by ensuring that words in each"
" column are separated by at least one space. "
" Further, allow for each word in a column to be either left"
"justified, right justified, or center justified within its column. "]
```
For this task, you will produce two files:
-`/lib/titling/hoon`
Your library `/lib/titling/hoon` should expose the following arms:
-`++align` accepts a `?(%left %center %right)` tag indicating the kind of alignment; a total width which should be checked to make sure it is greater than the input string; and a `tape` string containing the text to be aligned. It should return the appropriately aligned text as described above.
-`++box-text` accepts a `@ud` gap distance and a string, and returns the corresponding `wall` text string with single-line box-drawing characters surrounding the given text.
-`++double-box-text` accepts a `@ud` gap distance and a string, and returns the corresponding `wall` text string with double-line box-drawing characters surrounding the given text.
-`++symbol-box-text` accepts a `@t` single character symbol, a `@ud` gap distance, and a string, and returns the corresponding `wall` text string with the given symbol surrounding the given text at the correct gap distance.
-`/lib/columns/hoon`
Your library `/lib/columns/hoon` should expose the following arm:
-`++columnify` accepts a `?(%left %center %right)` tag indicating the kind of alignment and a `wall` of strings containing the text to be aligned. It reformats the text into a `wall` with the appropriate behavior as shown above.