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