Skip to content

Instantly share code, notes, and snippets.

@dschinkel
Last active May 28, 2019 00:30
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 dschinkel/11577f5ad1ff65459bb8658699f694be to your computer and use it in GitHub Desktop.
Save dschinkel/11577f5ad1ff65459bb8658699f694be to your computer and use it in GitHub Desktop.
Roman Numeral Calculator kata - Kotlin
package roman.numeral.calculator.kotlin
class Calculator {
private val toRoman = mapOf(
"IIIII" to "V",
"VV" to "X",
"XXXXX" to "L",
"LL" to "C",
"CCCCC" to "D",
"DD" to "M"
)
private val orderOfPrecedence = arrayOf('I', 'V', 'X', 'L', 'C', 'D', 'M')
fun add(romanNumeral1: String, romanNumeral2: String): String {
var total = "$romanNumeral1$romanNumeral2"
if(subtractedFromMoreThanOneOfSameNumeral(romanNumeral1, romanNumeral2)) return "$romanNumeral2$romanNumeral1"
if(subtractedFromMoreThanTwoNextHighestValues(total)) return "Illegal Subtraction"
if(ignoreIllegalSubtraction(total)) return total
total = swapForIllegalSubtraction(romanNumeral1, romanNumeral2, total)
total = replaceNumeralMatches(total)
return total
}
private fun subtractedFromMoreThanOneOfSameNumeral(romanNumeral1: String, romanNumeral2: String) =
romanNumeral1.toCharArray().count { it == 'I' || it == 'X' || it == 'C' || it == 'D' } == romanNumeral1.count()
&& romanNumeral1.count() > 1
&& romanNumeral1.first() != romanNumeral2.first()
private fun subtractedFromMoreThanTwoNextHighestValues(total: String): Boolean {
val totalChars = total.toCharArray()
var previousCharIndex = 0
totalChars.forEachIndexed { currentCharIndex, numeral ->
val currentCharPrecedence = orderOfPrecedence.indexOf(numeral)
val nextCharPrecedence = orderOfPrecedence.indexOf(totalChars[previousCharIndex + 1])
if (subtractedFromPositionHigherThanTwo(nextCharPrecedence, currentCharPrecedence)) return true
previousCharIndex = currentCharIndex
}
return false
}
private fun subtractedFromPositionHigherThanTwo(nextCharPrecedence: Int, currentCharPrecedence: Int): Boolean {
if (nextCharPrecedence - currentCharPrecedence > 2) {
return true
}
return false
}
private fun ignoreIllegalSubtraction(total: String): Boolean {
return listOf("VI", "LX", "DC").filter { total == it }.count() > 0
}
private fun swapForIllegalSubtraction(romanNumeral1: String, romanNumeral2: String, total: String): String {
var total1 = total
if (isIllegalSubtraction(romanNumeral1)) {
total1 = "$romanNumeral2$romanNumeral1"
}
return total1
}
private fun isIllegalSubtraction(romanNumeral1: String): Boolean {
return listOf("V", "L", "D").filter { romanNumeral1.contains(it) }.count() > 0
}
private fun replaceNumeralMatches(total: String): String {
var total1 = total
toRoman.forEach { (k, v) ->
total1 = total1.replace(k, v)
}
return total1
}
}
/*
I == 1
V == 5
X == 10
L == 50
C == 100
D == 500
M == 1000
The symbols I, X, C, and M can be repeated at most 3 times in a row
V, L, and D can never be repeated
As arabic numbers can be split into their constituent parts (1066 becomes 1 0 6 6), so too can Roman numerals. 1066 becomes MLXVI, or M (1000) LX (60) and VI (6)
The symbols (I, X, and C) can only be subtracted from the 2 next higher values. For example,
IV and IX -- (usage of I in subtraction rule)
XL and XC -- (usage of X in subtraction rule)
CD and CM -- (usage of C in subtraction rule)
The symbols (V, L, and D) can never be subtracted.
Only one subtraction can be made per numeral (XC is allowed, XXC is not). (Dave: so need to swap if repeated)
*/
package roman.numeral.calculator.kotlin
import kotlin.test.Test
import kotlin.test.*
class RomanNumeralCalculatorKata {
private var calculator = Calculator()
private lateinit var total: String
@Test
internal fun `one plus one is two`() {
total = calculator.add("I", "I")
assertEquals("II", total)
}
@Test
fun `one plus two is three`() {
total = calculator.add("I", "II")
assertEquals(total, "III")
}
@Test
fun `two plus three is five`() {
total = calculator.add("II", "III")
assertEquals(total, "V")
}
@Test
fun `five plus five is ten`() {
total = calculator.add("V", "V")
assertEquals(total, "X")
}
@Test
fun `thirty plus twenty is fifty`() {
total = calculator.add("XXX", "XX")
assertEquals(total, "L")
}
@Test
fun `fifty plus fifty is one hundred`() {
total = calculator.add("L", "L")
assertEquals(total, "C")
}
@Test
fun `four hundred plus one hundred is five hundred`() {
total = calculator.add("CCCC", "C")
assertEquals(total, "D")
}
@Test
fun `five hundred plus five hundred is one thousand`() {
total = calculator.add("D", "D")
assertEquals(total, "M")
}
@Test
fun `three plus three is six`() {
total = calculator.add("III", "III")
assertEquals(total, "VI")
}
@Test
fun `four plus ten is fourteen`() {
total = calculator.add("IV", "X")
assertEquals(total, "XIV")
}
@Test
fun `five plus ten is fifteen`() {
total = calculator.add("V", "X")
assertEquals("XV", total)
}
@Test
fun `fifty plus one hundred is one hundred fifty`() {
total = calculator.add("L", "C")
assertEquals("CL", total)
}
@Test
fun `five hundred plus one thousand is fifteen hundred`() {
total = calculator.add("D", "M")
assertEquals("MD", total)
}
@Test
fun `six plus ten is sixteen`() {
total = calculator.add("VI", "X")
assertEquals("XVI", total)
}
@Test
fun `one hundred minus five hundred is four hundred`() {
total = calculator.add("C", "D")
assertEquals("CD", total)
}
@Test
fun `five plus one is six`() {
total = calculator.add("V", "I")
assertEquals("VI", total)
}
@Test
fun `fifty plus ten is sixty`() {
total = calculator.add("L", "X")
assertEquals("LX", total)
}
@Test
fun `five hundred plus one hundred is six hundred`() {
total = calculator.add("D", "C")
assertEquals("DC", total)
}
@Test
fun `one plus ten is eleven`() {
total = calculator.add("I", "X")
assertEquals("IX", total)
}
@Test
fun `can only subtract from next two highest values`() {
val illegalAdds = arrayOf(
Pair("I", "L"),
Pair("I", "C"),
Pair("I", "D"),
Pair("I", "M"),
Pair("V", "C"),
Pair("V", "D"),
Pair("V", "M"),
Pair("X", "D"),
Pair("X", "M"),
Pair("L", "M")
)
illegalAdds.forEach { pair ->
total = calculator.add(pair.first, pair.second)
assertEquals("Illegal Subtraction", total)
}
}
@Test
fun `only one subtraction can be made per numeral`() {
val illegalAdds = arrayOf(
Pair("II", "V"),
Pair("XX", "C"),
Pair("CC", "D")
)
illegalAdds.forEach { pair ->
total = calculator.add(pair.first, pair.second)
val swappedInputs = "${pair.second}${pair.first}"
assertEquals(swappedInputs, total)
}
}
@Test
fun `three plus twenty is twenty three`() {
total = calculator.add("III", "XX")
assertEquals("XXIII", total)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment