// // Roman.swift // RegexInSwift : use regex to parse a string with a Roman number and returns its decimal value // // Created by Guy Lapalme on 2024-07-26. // let romanRE = #/ (M{1,3})? # thousands (C(?:M|D|C{,2})|DC{,3})? # hundreds (X(?:C|L|X{,2})|LX{,3})? # tens (I(?:X|V|I{,2})|VI{,3})? # units /# let romanVals = [ "C": 100, "CC": 200, "CCC": 300, "CD": 400, "CM": 900, "D": 500, "DC": 600, "DCC": 700, "DCCC": 800, "I": 1, "II": 2, "III": 3, "IV": 4, "IX": 9, "L": 50, "LX": 60, "LXX": 70, "LXXX": 80, "M": 1000, "MM": 2000, "MMM": 3000, "V": 5, "VI": 6, "VII": 7, "VIII": 8, "X": 10, "XC": 90, "XL": 40, "XX": 20, "XXX": 30 ] func parseRomanRE(_ s:String)->Int? { if let m = s.wholeMatch(of: romanRE){ var res = 0 if let v = m.output.1 {res += romanVals["\(v)"]!} if let v = m.output.2 {res += romanVals["\(v)"]!} if let v = m.output.3 {res += romanVals["\(v)"]!} if let v = m.output.4 {res += romanVals["\(v)"]!} return res } return nil } // //////////////////////////////////////////////// // using a RegexBuilder // import RegexBuilder func makeRB(_ i: String,_ v:String, _ x:String)->Capture<(Substring, Int)>{ return Capture { ChoiceOf { Regex {i ; ChoiceOf { x; v ; Repeat(...2) { i }}} Regex { v; Repeat(...3) { i }} } } transform:{romanVals["\($0)"]!} } let romanRB = Regex { Optionally {Capture { Repeat(1...3){"M"} } transform: {str in 1000*str.count}} Optionally {makeRB("C","D","M")} Optionally {makeRB("X","L","C")} Optionally {makeRB("I","V","X")} } func parseRomanRB(_ s:String)->Int? { if let m = s.wholeMatch(of: romanRB){ let out = m.output return (out.1 ?? 0) + (out.2 ?? 0) + (out.3 ?? 0) + (out.4 ?? 0) } return nil } // a few tests, not at all exhaustive! func checkRoman(_ s:String,_ parse:(String)->Int?){ print(s," => ", parse(s) ?? "illegal roman numeral") } func runRoman(){ print("** parseRomanRE tests") checkRoman("VII",parseRomanRE) checkRoman("XM",parseRomanRE) checkRoman("MMCDLXXVI",parseRomanRE) print("** parseRomanRB tests") checkRoman("VIII",parseRomanRB) checkRoman("DM",parseRomanRB) checkRoman("MMMDCCCLXVII",parseRomanRB) }