| """Convert to and from Roman numerals""" |
| |
| __author__ = "Mark Pilgrim (f8dy@diveintopython.org)" |
| __version__ = "1.4" |
| __date__ = "8 August 2001" |
| __copyright__ = """Copyright (c) 2001 Mark Pilgrim |
| |
| This program is part of "Dive Into Python", a free Python tutorial for |
| experienced programmers. Visit http://diveintopython.org/ for the |
| latest version. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the Python 2.1.1 license, available at |
| http://www.python.org/2.1.1/license.html |
| """ |
| |
| import re |
| |
| #Define exceptions |
| class RomanError(Exception): pass |
| class OutOfRangeError(RomanError): pass |
| class NotIntegerError(RomanError): pass |
| class InvalidRomanNumeralError(RomanError): pass |
| |
| #Define digit mapping |
| romanNumeralMap = (('M', 1000), |
| ('CM', 900), |
| ('D', 500), |
| ('CD', 400), |
| ('C', 100), |
| ('XC', 90), |
| ('L', 50), |
| ('XL', 40), |
| ('X', 10), |
| ('IX', 9), |
| ('V', 5), |
| ('IV', 4), |
| ('I', 1)) |
| |
| def toRoman(n): |
| """convert integer to Roman numeral""" |
| if not (0 < n < 5000): |
| raise OutOfRangeError("number out of range (must be 1..4999)") |
| if int(n) != n: |
| raise NotIntegerError("decimals can not be converted") |
| |
| result = "" |
| for numeral, integer in romanNumeralMap: |
| while n >= integer: |
| result += numeral |
| n -= integer |
| return result |
| |
| #Define pattern to detect valid Roman numerals |
| romanNumeralPattern = re.compile(""" |
| ^ # beginning of string |
| M{0,4} # thousands - 0 to 4 M's |
| (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), |
| # or 500-800 (D, followed by 0 to 3 C's) |
| (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), |
| # or 50-80 (L, followed by 0 to 3 X's) |
| (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), |
| # or 5-8 (V, followed by 0 to 3 I's) |
| $ # end of string |
| """ ,re.VERBOSE) |
| |
| def fromRoman(s): |
| """convert Roman numeral to integer""" |
| if not s: |
| raise InvalidRomanNumeralError('Input can not be blank') |
| if not romanNumeralPattern.search(s): |
| raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s) |
| |
| result = 0 |
| index = 0 |
| for numeral, integer in romanNumeralMap: |
| while s[index:index+len(numeral)] == numeral: |
| result += integer |
| index += len(numeral) |
| return result |