Welcome, guest | Sign In | My Account | Store | Cart
'''
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
            

History