|
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 |