Skip to content

Instantly share code, notes, and snippets.

@rkie
Created April 2, 2023 14:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rkie/62aa1c894d25b8189aa8f01356945ae6 to your computer and use it in GitHub Desktop.
Save rkie/62aa1c894d25b8189aa8f01356945ae6 to your computer and use it in GitHub Desktop.
Used to demonstrate test driven refactoring in https://failedtofunction.com/test-driven-refactoring/
package com.failedtofunction.examples.numerals
class RomanNumeral {
private val value: Int
private val representation: String
private constructor(value: Int, representation: String) {
this.value = value
this.representation = representation
}
fun toInt(): Int = value
override fun toString(): String = representation
inline operator fun plus(another: RomanNumeral): RomanNumeral = fromInt(toInt() + another.toInt())
override fun equals(other: Any?): Boolean {
return if (other is RomanNumeral) {
value == other.value
} else {
false
}
}
override fun hashCode(): Int {
return value.hashCode()
}
companion object {
private val descendingMap: Map<String, Int> = listOf(
"M" to 1000, "CM" to 900, "D" to 500, "CD" to 400,
"C" to 100, "XC" to 90, "L" to 50, "XL" to 40,
"X" to 10, "IX" to 9, "V" to 5, "IV" to 4, "I" to 1
).toMap()
private fun toRomanNumeral(value: Int): String {
var num = value
var result = ""
for ((numeral, value) in descendingMap.entries) {
while (num >= value) {
num -= value
result += numeral
}
}
return result
}
fun fromString(rep: String): RomanNumeral {
val value = rep
.toCharArray()
.reversed()
.map { descendingMap[it.toString()]!! }
.fold(Pair(0, 0)) { state, item ->
val maxVal = kotlin.math.max(state.first, item)
val runningTotal = if (item >= state.first) {
state.second + item
} else {
state.second - item
}
Pair(maxVal, runningTotal)
}.second
return RomanNumeral(value, rep)
}
fun fromInt(value: Int): RomanNumeral = RomanNumeral(value, toRomanNumeral(value))
}
}
package com.failedtofunction.examples.numerals
import kotlin.test.Test
import kotlin.test.assertEquals
class RomanNumeralTest {
@Test
fun `should add two Roman Numerals together showing the result as a Roman Numeral`() {
val four = RomanNumeral.fromString("IV")
val eleven = RomanNumeral.fromString("XI")
val expected = RomanNumeral.fromString("XV")
val result = four + eleven
assertEquals(expected, result)
}
@Test
fun `should be able to access the Int value of a Roman Numeral created from the String representation`() {
val input = RomanNumeral.fromString("IV")
val expected = 4
val result = input.toInt()
assertEquals(expected, result)
}
@Test
fun `should show the Roman Numeral representation when calling toString()`() {
val input = RomanNumeral.fromInt(4)
val expected = "IV"
val result = input.toString()
assertEquals(expected, result)
}
@Test
fun `should verify that equivalent Roman Numerals created from Int and String are equal`() {
val input = 4
val expected = RomanNumeral.fromString("IV")
val result = RomanNumeral.fromInt(input)
assertEquals(expected, result)
}
@Test
fun `should verify that Roman numbers with equivalent values created from either Int or String have the same hash`() {
val first = RomanNumeral.fromString("XX")
val second = RomanNumeral.fromInt(20)
assertEquals(first.hashCode(), second.hashCode())
}
@Test
fun `should convert integers from 1 to 1000 to Roman Numerals and back`() {
for (i in 1..1000) {
val asRomanNumeral = RomanNumeral.fromInt(i)
val asInteger = RomanNumeral.fromString(asRomanNumeral.toString())
assertEquals(i, asInteger.toInt(), "Incorrect conversion of $i either to Roman Numeral $asRomanNumeral or back to $asInteger")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment