Last active
May 28, 2019 00:30
-
-
Save dschinkel/11577f5ad1ff65459bb8658699f694be to your computer and use it in GitHub Desktop.
Roman Numeral Calculator kata - Kotlin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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