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