This Roman class is a subclass of int and supports the same methods int does, but any special methods that would normally return ints are return a new instance of Roman. You can use instances of this class in math expressions and a Roman instance will be returned, for example.
The class decorator used to achieve this was suggested by Alex Martelli 'here on stackoverlow.com.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | '''
The int methods of this Roman class are overloaded to return new
Roman instances, so you can use it to perform wrap ints and
perform basic math, like this:
>>> x = Roman(3)
>>> y = Roman(30)
>>> print x, y
Roman(3, III) Roman(30, XXX)
>>> print x + y
Roman(33, XXXIII)
>>> print x * y
Roman(90, LXXXX)
>>> print y / x
Roman(10, X)
>>> print Roman(2) ** 3
Roman(8, VIII)
>>> print Roman(25) % 11
Roman(3, III)
>>> print Roman(-45)
Roman(-45, -DCCCCLV)
>>> sum(map(Roman, range(10)))
Roman(45)
>>> reduce(operator.mul, map(Roman, range(1, 5)))
Roman(24)
>>> print sum(map(Roman, range(10)))
Roman(45, XXXXV)
>>> print reduce(operator.mul, map(Roman, range(1, 5)))
Roman(24, XXIV)
'''
__author__ = "Thom"
__copyright__ = None
__license__ = "Python"
import re
def returnthisclassfrom(methods):
'''
Class decorator by Alex Martelli.
see ('http://stackoverflow.com/questions/1242589/
subclassing-int-to-attain-a-hex-representation/1243045#1243045)
'''
def wrapit(cls, method):
return lambda *a: cls(method(*a))
def dowrap(cls):
for n in methods:
method = getattr(cls, n)
setattr(cls, n, wrapit(cls, method))
return cls
return dowrap
methods = "mul add sub div pow mod divmod".split()
methods += [c + s for s in methods for c in list('ir')]
methods = ['__%s__' % s for s in methods]
methods = set(methods) & set(dir(int))
@returnthisclassfrom(methods)
class Roman(int):
i_ones = range(10)
r_ones = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX']
ones_i2r = dict(zip(i_ones, r_ones))
i_mul = [10, 50, 100, 500, 1000]
r_mul = ["X", "L", "C", "D", "M"]
mul_i2r = dict(zip(i_mul, r_mul))
i = i_ones + i_mul
r = r_ones + r_mul
rom2int = dict(zip(r, i))
re_rom = re.compile('|'.join(sorted(r, key=len, reverse=True)))
def as_roman(self):
sign = "-" if self < 0 else ""
res = [sign]
n = int(self)
mul_i2r = self.mul_i2r
for x in reversed(self.i_mul):
div, mod = divmod(n, x)
n = mod
res.append(mul_i2r[x] * div)
res.append(self.ones_i2r[n])
return "".join(res)
def __str__(self):
return "Roman(%d, %s)" % (self, self.as_roman())
def __repr__(self):
return "Roman(%d)" % self
@classmethod
def fromstring(cls, s):
return cls(sum(map(cls.rom2int.get, cls.re_rom.findall(s))))
if __name__ == "__main__":
for x in range(1, 1001):
r = Roman(x)
assert r == Roman.fromstring(r.as_roman())
print x, r
print '\n'
import operator
x, y = Roman(20), Roman(5)
print 'x =', x
print 'y = ', y
for s in "add sub div mul".split():
print '%s(x, y) = %s' % (s, getattr(operator, s)(x, y))
print '\n'
x -= 1
print 'x % y = ', x % y
|
">>> print Roman(-45)"
"Roman(-45, -DCCCCLV)"
There is no such thing as a Roman numeral style negative number. Their numbering system had no concept of zero either.
I just invented a negative roman numeral then. So now it exists.
Some interesting pieces of code brought together here, thanks!
However, a basic rule of Roman Numeral representation has been broken, viz. - a sequence of 4 or more identical adjacent numerals is not allowed, as in your example:
Roman(90, LXXXX)
90 is represented as XC, which is why it is not exactly trivial to create Roman Numeral sequences using only mathematical operations like +, *, /, or %; some form of lookup is also required so as to conform with accepted representations.
I'm afraid you're quite right Pat--and now when I need to handle Roman numerals I just use a pair of mappings.