Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 135 lines
 ``` 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 ``` Barry Walker 12 years, 1 month ago

">>> 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. thom neale (author) 11 years, 3 months ago

I just invented a negative roman numeral then. So now it exists. Pat O'Brien 9 years, 2 months ago

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. thom neale (author) 9 years, 2 months ago

I'm afraid you're quite right Pat--and now when I need to handle Roman numerals I just use a pair of mappings. Created by thom neale on Wed, 6 Apr 2011 (MIT)