From c3db6f200c4cc73affa40125452c30785a9574d3 Mon Sep 17 00:00:00 2001 From: Daniel David Kovacs Date: Tue, 22 Mar 2016 13:37:24 +0100 Subject: [PATCH 1/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 85dcad4..07cbcf9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # romans +> All right, but apart from the sanitation, the medicine, education, wine, public order, irrigation, roads, the fresh-water system, and public health, what have the Romans ever done for us? [![Build Status](https://travis-ci.org/HcomCoolCode/romans.svg?branch=master)](https://travis-ci.org/HcomCoolCode/romans) From aa31f39701b196b821df785f822ac72b77ae6676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20D=C3=A1niel=20D=C3=A1vid?= Date: Thu, 24 Mar 2016 12:47:30 +0100 Subject: [PATCH 2/5] first draft roman to arabic --- roman-numbers.playground/Contents.swift | 90 ++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 2 deletions(-) diff --git a/roman-numbers.playground/Contents.swift b/roman-numbers.playground/Contents.swift index 313d2b8..035ec59 100644 --- a/roman-numbers.playground/Contents.swift +++ b/roman-numbers.playground/Contents.swift @@ -2,8 +2,85 @@ import UIKit +enum RomanNumeralSymbol: Int { + case I = 1 + case V = 5 + case X = 10 + case L = 50 + case C = 100 + case D = 500 + case M = 1000 + static func parse(char: Character) -> RomanNumeralSymbol? { + switch char { + case "I": + return I + case "V": + return V + case "X": + return X + case "L": + return L + case "C": + return C + case "D": + return D + case "M": + return M + default: + return nil + } + } +} + +struct Token { + var symbol: RomanNumeralSymbol + var count: Int +} + +func parseRomanNumeral(romanNumber: String) -> [Token] { + var tokens = [Token]() + romanNumber.characters.forEach({(char: Character) in + if let nextSymbol = RomanNumeralSymbol.parse(char) { + if tokens.count < 1 || tokens.last!.symbol != nextSymbol { + tokens.append(Token(symbol: nextSymbol, count: 1)) + } else { + let oldToken = tokens.popLast()! + // validate count? + tokens.append(Token(symbol: nextSymbol, count: oldToken.count + 1)); + } + } else { + // todo! handle invalid token + } + }) + return tokens +} + +func processRomanNumeralTokens(tokens: [Token]) -> Int { + var acc = 0 + let tokenCount = tokens.count + for i in 0.. Int { - return romanNumber.characters.count + return processRomanNumeralTokens(parseRomanNumeral(romanNumber)) } func numToRoman(number: Int) -> String { @@ -21,4 +98,13 @@ assert(romanToNum(numToRoman(three)) == three, "3 -> III -> 3") assert(numToRoman(romanToNum(III)) == III, "III -> 3 -> III") let IV = "IV" -assert(romanToNum(IV) == 4, "IV is four") \ No newline at end of file +assert(romanToNum(IV) == 4, "IV is four") + +assert(romanToNum("II") == 2, "II is 2") +assert(romanToNum("III") == 3, "III is 3") +assert(romanToNum("IV") == 4, "IV is 4") +assert(romanToNum("V") == 5, "V is 5") +assert(romanToNum("VI") == 6, "VI is 6") +assert(romanToNum("VIII") == 8, "VIII is 8") +assert(romanToNum("MMXVI") == 2016, "MMXVI is 2016") + From d545e38d9576eae1464f6ff10d7b45cf89f51b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20D=C3=A1niel=20D=C3=A1vid?= Date: Thu, 24 Mar 2016 12:57:54 +0100 Subject: [PATCH 3/5] some more tests --- roman-numbers.playground/Contents.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/roman-numbers.playground/Contents.swift b/roman-numbers.playground/Contents.swift index 035ec59..6d0ed45 100644 --- a/roman-numbers.playground/Contents.swift +++ b/roman-numbers.playground/Contents.swift @@ -105,6 +105,8 @@ assert(romanToNum("III") == 3, "III is 3") assert(romanToNum("IV") == 4, "IV is 4") assert(romanToNum("V") == 5, "V is 5") assert(romanToNum("VI") == 6, "VI is 6") +assert(romanToNum("IX") == 9, "IX is 9") assert(romanToNum("VIII") == 8, "VIII is 8") +assert(romanToNum("MCMLXXXI") == 1981, "MCMLXXXI is 1981") assert(romanToNum("MMXVI") == 2016, "MMXVI is 2016") From a18126f2df144b353a8880bd3126b3e03029dccc Mon Sep 17 00:00:00 2001 From: Daniel David Kovacs Date: Thu, 24 Mar 2016 19:06:17 +0100 Subject: [PATCH 4/5] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 07cbcf9..dbcc3dc 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,6 @@ [![Build Status](https://travis-ci.org/HcomCoolCode/romans.svg?branch=master)](https://travis-ci.org/HcomCoolCode/romans) +![whtredfu](http://blogs.telegraph.co.uk/culture/files/2011/01/What-have2.jpg) + Write a function to convert roman numerals to hindu-arabic numbers and vice versa. From f9bd1fb6232c14d78059e70f826a142a4b21cdb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kov=C3=A1cs=20D=C3=A1niel=20D=C3=A1vid?= Date: Wed, 30 Mar 2016 09:17:17 +0200 Subject: [PATCH 5/5] full solution back and forth --- roman-numbers.playground/Contents.swift | 186 +++++++++++++++--------- 1 file changed, 121 insertions(+), 65 deletions(-) diff --git a/roman-numbers.playground/Contents.swift b/roman-numbers.playground/Contents.swift index 6d0ed45..f6fc9bd 100644 --- a/roman-numbers.playground/Contents.swift +++ b/roman-numbers.playground/Contents.swift @@ -2,6 +2,35 @@ import UIKit +/*: + I, V, X + | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | + | --- | --- | --- | --- | --- | --- | --- | --- | --- | + | I | II | III | IV | V | VI | VII | VIII| IX | + + X, L, C + | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | + | --- | --- | --- | --- | --- | --- | --- | --- | --- | + | X | XX | XXX | XL | L | LX | LXX | LXXX| XC | + + C, D, M + | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | + | --- | --- | --- | --- | --- | --- | --- | --- | --- | + | C | CC | CCC | CD | D | DC | DCC | DCCC| CM | + + M + | 1000| 2000| 3000| + | --- | --- | --- | + | M | MM | MMM | + + */ + +extension String { + static func fromRepeatsOf(count count: Int, repeatedValue: String) -> String { + return Array(count: count, repeatedValue: repeatedValue).joinWithSeparator("") + } +} + enum RomanNumeralSymbol: Int { case I = 1 case V = 5 @@ -10,84 +39,94 @@ enum RomanNumeralSymbol: Int { case C = 100 case D = 500 case M = 1000 - static func parse(char: Character) -> RomanNumeralSymbol? { - switch char { - case "I": - return I - case "V": - return V - case "X": - return X - case "L": - return L - case "C": - return C - case "D": - return D - case "M": - return M - default: - return nil + + static let pivots = [V, L, D] + private static let previousSymbol: Dictionary = [I: nil, V: I, X: V, L: X, C: L, D: C, M: D] + private static let nextSymbol: Dictionary = [I: V, V: X, X: L, L: C, C: D, D: M, M: nil] + private static let symbolTable = [I: "I", V: "V", X: "X", L: "L", C: "C", D: "D", M: "M"] + private static let parseTable = ["I": I, "V": V, "X": X, "L": L, "C": C, "D": D, "M": M] + + var string: String { + get { + return RomanNumeralSymbol.symbolTable[self]! + } + } + var prev: RomanNumeralSymbol? { + get { + return RomanNumeralSymbol.previousSymbol[self]! } } + var next: RomanNumeralSymbol? { + get { + return RomanNumeralSymbol.nextSymbol[self]! + } + } + static func parse(char: Character) -> RomanNumeralSymbol? { + return RomanNumeralSymbol.parseTable[String(char)] + } } -struct Token { - var symbol: RomanNumeralSymbol - var count: Int -} +// NB! No validation on roman numeral string +func romanToNum(romanNumber: String) -> Int { + struct Token { + var symbol: RomanNumeralSymbol + var count: Int + } -func parseRomanNumeral(romanNumber: String) -> [Token] { - var tokens = [Token]() - romanNumber.characters.forEach({(char: Character) in - if let nextSymbol = RomanNumeralSymbol.parse(char) { - if tokens.count < 1 || tokens.last!.symbol != nextSymbol { - tokens.append(Token(symbol: nextSymbol, count: 1)) + func parseTokens(romanNumber: String) -> [Token] { + var tokens = [Token]() + romanNumber.characters.forEach({(char: Character) in + if let nextSymbol = RomanNumeralSymbol.parse(char) { + if tokens.count < 1 || tokens.last!.symbol != nextSymbol { + tokens.append(Token(symbol: nextSymbol, count: 1)) + } else { + let oldToken = tokens.popLast()! + tokens.append(Token(symbol: nextSymbol, count: oldToken.count + 1)); + } + } + }) + return tokens + } + + func evaluateTokens(tokens: [Token]) -> Int { + var accumulator = 0 + let tokenCount = tokens.count + for i in 0.. Int { - var acc = 0 - let tokenCount = tokens.count - for i in 0.. Int { - return processRomanNumeralTokens(parseRomanNumeral(romanNumber)) + return evaluateTokens(parseTokens(romanNumber)) } func numToRoman(number: Int) -> String { - let romanArray = [Character](count: number, repeatedValue: "I") - return String(romanArray) -} + func thousandsOf(number: Int) -> String { + return String.fromRepeatsOf(count: number / 1000, repeatedValue: RomanNumeralSymbol.M.string) + } + + // pivot: middle symbol in the range of hundreds / tens / ones, i.e: D, L, V + func lowerRanksOf(number: Int, pivot: RomanNumeralSymbol) -> String { + let modulo = (number / pivot.prev!.rawValue) % 10 + switch modulo { + case 0: return "" + case 1...3: return String.fromRepeatsOf(count: modulo, repeatedValue: pivot.prev!.string) + case 4...8: return String.fromRepeatsOf(count: max(5 - modulo, 0), repeatedValue: pivot.prev!.string) + + pivot.string + + String.fromRepeatsOf(count: max(modulo - 5, 0), repeatedValue: pivot.prev!.string) + case 9: return pivot.prev!.string + pivot.next!.string + default: return "?" + } + } + return thousandsOf(number) + RomanNumeralSymbol.pivots.map({lowerRanksOf(number, pivot: $0)}).reduce("", combine: {$1 + $0}) +} let III = "III" let three = 3 @@ -100,13 +139,30 @@ assert(numToRoman(romanToNum(III)) == III, "III -> 3 -> III") let IV = "IV" assert(romanToNum(IV) == 4, "IV is four") +// roman -> arabic assert(romanToNum("II") == 2, "II is 2") assert(romanToNum("III") == 3, "III is 3") assert(romanToNum("IV") == 4, "IV is 4") assert(romanToNum("V") == 5, "V is 5") assert(romanToNum("VI") == 6, "VI is 6") -assert(romanToNum("IX") == 9, "IX is 9") assert(romanToNum("VIII") == 8, "VIII is 8") +assert(romanToNum("IX") == 9, "IX is 9") +assert(romanToNum("XVII") == 17, "XVII is 17") +assert(romanToNum("XXXIX") == 39, "XXXIX is 39") +assert(romanToNum("LXXXIX") == 89, "LXXXIX is 89") assert(romanToNum("MCMLXXXI") == 1981, "MCMLXXXI is 1981") assert(romanToNum("MMXVI") == 2016, "MMXVI is 2016") +// arabic -> roman +assert(numToRoman(2) == "II", "II is 2") +assert(numToRoman(3) == "III", "III is 3") +assert(numToRoman(4) == "IV", "IV is 4") +assert(numToRoman(5) == "V", "V is 5") +assert(numToRoman(6) == "VI", "VI is 6") +assert(numToRoman(8) == "VIII", "VIII is 8") +assert(numToRoman(9) == "IX", "IX is 9") +assert(numToRoman(17) == "XVII", "XVII is 17") +assert(numToRoman(39) == "XXXIX", "XXXIX is 39") +assert(numToRoman(89) == "LXXXIX", "LXXXIX is 89") +assert(numToRoman(1981) == "MCMLXXXI", "MCMLXXXI is 1981") +assert(numToRoman(2016) == "MMXVI", "MMXVI is 2016")