| 1 | """Convert to and from Roman numerals"""
|
|---|
| 2 |
|
|---|
| 3 | __author__ = "Mark Pilgrim (f8dy@diveintopython.org)"
|
|---|
| 4 | __version__ = "1.4"
|
|---|
| 5 | __date__ = "8 August 2001"
|
|---|
| 6 | __copyright__ = """Copyright (c) 2001 Mark Pilgrim
|
|---|
| 7 |
|
|---|
| 8 | This program is part of "Dive Into Python", a free Python tutorial for
|
|---|
| 9 | experienced programmers. Visit http://diveintopython.org/ for the
|
|---|
| 10 | latest version.
|
|---|
| 11 |
|
|---|
| 12 | This program is free software; you can redistribute it and/or modify
|
|---|
| 13 | it under the terms of the Python 2.1.1 license, available at
|
|---|
| 14 | http://www.python.org/2.1.1/license.html
|
|---|
| 15 | """
|
|---|
| 16 |
|
|---|
| 17 | import re
|
|---|
| 18 |
|
|---|
| 19 | #Define exceptions
|
|---|
| 20 | class RomanError(Exception): pass
|
|---|
| 21 | class OutOfRangeError(RomanError): pass
|
|---|
| 22 | class NotIntegerError(RomanError): pass
|
|---|
| 23 | class InvalidRomanNumeralError(RomanError): pass
|
|---|
| 24 |
|
|---|
| 25 | #Define digit mapping
|
|---|
| 26 | romanNumeralMap = (('M', 1000),
|
|---|
| 27 | ('CM', 900),
|
|---|
| 28 | ('D', 500),
|
|---|
| 29 | ('CD', 400),
|
|---|
| 30 | ('C', 100),
|
|---|
| 31 | ('XC', 90),
|
|---|
| 32 | ('L', 50),
|
|---|
| 33 | ('XL', 40),
|
|---|
| 34 | ('X', 10),
|
|---|
| 35 | ('IX', 9),
|
|---|
| 36 | ('V', 5),
|
|---|
| 37 | ('IV', 4),
|
|---|
| 38 | ('I', 1))
|
|---|
| 39 |
|
|---|
| 40 | def toRoman(n):
|
|---|
| 41 | """convert integer to Roman numeral"""
|
|---|
| 42 | if not (0 < n < 5000):
|
|---|
| 43 | raise OutOfRangeError("number out of range (must be 1..4999)")
|
|---|
| 44 | if int(n) != n:
|
|---|
| 45 | raise NotIntegerError("decimals can not be converted")
|
|---|
| 46 |
|
|---|
| 47 | result = ""
|
|---|
| 48 | for numeral, integer in romanNumeralMap:
|
|---|
| 49 | while n >= integer:
|
|---|
| 50 | result += numeral
|
|---|
| 51 | n -= integer
|
|---|
| 52 | return result
|
|---|
| 53 |
|
|---|
| 54 | #Define pattern to detect valid Roman numerals
|
|---|
| 55 | romanNumeralPattern = re.compile("""
|
|---|
| 56 | ^ # beginning of string
|
|---|
| 57 | M{0,4} # thousands - 0 to 4 M's
|
|---|
| 58 | (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
|
|---|
| 59 | # or 500-800 (D, followed by 0 to 3 C's)
|
|---|
| 60 | (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
|
|---|
| 61 | # or 50-80 (L, followed by 0 to 3 X's)
|
|---|
| 62 | (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
|
|---|
| 63 | # or 5-8 (V, followed by 0 to 3 I's)
|
|---|
| 64 | $ # end of string
|
|---|
| 65 | """ ,re.VERBOSE)
|
|---|
| 66 |
|
|---|
| 67 | def fromRoman(s):
|
|---|
| 68 | """convert Roman numeral to integer"""
|
|---|
| 69 | if not s:
|
|---|
| 70 | raise InvalidRomanNumeralError('Input can not be blank')
|
|---|
| 71 | if not romanNumeralPattern.search(s):
|
|---|
| 72 | raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s)
|
|---|
| 73 |
|
|---|
| 74 | result = 0
|
|---|
| 75 | index = 0
|
|---|
| 76 | for numeral, integer in romanNumeralMap:
|
|---|
| 77 | while s[index:index+len(numeral)] == numeral:
|
|---|
| 78 | result += integer
|
|---|
| 79 | index += len(numeral)
|
|---|
| 80 | return result
|
|---|