Solves AOC 2022 with Type-Driven Development and Property-Based Testing
This kata comes from the advent of code 2022.
Design a system that can use
Snafunumbers and convert it back toDecimal.
Instead of using digits, the digits are 2, 1, 0, minus (written -), and double-minus (written =).
Minus is worth -1, and double-minus is worth -2.
Because ten (in normal numbers) is two fives and no ones, in SNAFU it is written 20.
Since eight (in normal numbers) is two fives minus two ones, it is written 2=.
You can do it the other direction, say you have the SNAFU number 2=-01:
- That's
2in the625splace =(double-minus) in the125splace-(minus) in the25splace0in the5splace1in the1splace
(
2x625) + (-2x125) + (-1x25) + (0x5) + (1x1) = 1250 + -250 + -25 + 0 + 1 = 976!
| Decimal | SNAFU |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | 1= |
| 4 | 1- |
| 5 | 10 |
| 6 | 11 |
| 7 | 12 |
| 8 | 2= |
| 9 | 2- |
| 10 | 20 |
| 15 | 1=0 |
| 20 | 1-0 |
| 2022 | 1=11-2 |
| 12345 | 1-0---0 |
| 314159265 | 1121-1110-1=0 |
Based on this process, here are some examples of Snafu counterparts in decimal:
| SNAFU | Decimal |
|---|---|
| 1=-0-2 | 1747 |
| 12111 | 906 |
| 2=0= | 198 |
| 21 | 11 |
| 2=01 | 201 |
| 111 | 31 |
| 20012 | 1257 |
| 112 | 32 |
| 1=-1= | 353 |
| 1-12 | 107 |
| 12 | 7 |
| 1= | 3 |
| 122 | 37 |
Minimum value for Snafu is 1 and its maximum value is 2222222222222222.
- Apply "Parse Don't Validate" principle
- Use "Property-Driven Development" a.k.a guiding the design of your types from
propertiesusing your favorite programming language and aProperty-Based Testinglibrary
- Start with a
parserthat always returnsRight<Snafu>- Write a minimalist data structure first (empty one)
- Your parser may look like this:
String -> Either<ParsingError, Snafu>
- Write a
positive propertychecking valid snafu can be round-tripped- Round-tripping:
Snafu -> String -> Snafu- Assert that round-tripped
Snafuequals originalSnafu
- Assert that round-tripped
- To do so, you will have to create your own valid
Snafugenerator forPBT
- Round-tripping:
// RoundTripping Snafu : `Snafu -> String -> Snafu`
for all (validSnafu)
parseSnafu(validSnafu.ToString()) == validSnafu
- Imagine
other propertiesand implement them
Use the identified properties to guide your implementation
You can design other properties to go further:
// Parsing an invalid snafu : `Invalid String -> ParsingError`
for all (invalidSnafu)
parseSnafu(invalidSnafu.ToString()) == left(ParsingError)
// Parsing a negative snafu : `negative snafu -> ParsingError`
for all (invalidSnafu)
parseSnafu(invalidSnafu.ToString()) == left(ParsingError)
// RoundTripping from decimal : `long -> Snafu -> long`
for all (validLong)
toNumber(toSnafu(validLong)) == validLong
// Decimals lower or equal 0 are not valid snafus : `long -> ParsingError`
for all (invalidLong)
toSnafu(validLong) == left(ParsingError)
// Decimals greater than maximum value : `long -> ParsingError`
for all (greaterThanMaxValueLong)
toSnafu(greaterThanMaxValueLong) == left(ParsingError)
// Snafu greater than maximum value : `String -> ParsingError`
for all (greaterThanMaxValueString)
toSnafu(greaterThanMaxValueString) == left(ParsingError)
A proposal of solution is available in C# with FsCheck and LanguageExt in the solution folder.