/Doc/tools/roman.py

http://unladen-swallow.googlecode.com/ · Python · 80 lines · 67 code · 8 blank · 5 comment · 4 complexity · 9c65b77db9b9eaff722ff9383213fb86 MD5 · raw file

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