## Task

Create a RomanNumerals class that can convert a roman numeral to and from an integer value.  It should follow the API demonstrated in the examples below. Multiple roman numeral values will be tested for each helper method. 

Modern Roman numerals are written by expressing each digit separately starting with the left most digit and skipping any digit with a value of zero. In Roman numerals 1990 is rendered: 1000=M, 900=CM, 90=XC; resulting in MCMXC. 2008 is written as 2000=MM, 8=VIII; or MMVIII. 1666 uses each Roman symbol in descending order: MDCLXVI.

## Examples

```python
RomanNumerals.to_roman(1000) # should return 'M'
RomanNumerals.from_roman('M') # should return 1000
```

## Help

| Symbol | Value |
|----------------|
| I          | 1     |
| V          | 5     |
| X          | 10    |
| L          | 50    |
| C          | 100   |
| D          | 500   |
| M          | 1000  |

Difficulty: interview

Starter code:

def to_roman(n):
	

Solutions:
import string
from collections import OrderedDict

class RomanNumerals:
  @classmethod
  def to_roman(self, num):
    conversions = OrderedDict([('M',1000), ('CM',900), ('D', 500), ('CD',400), ('C',100), ('XC',90), ('L',50), ('XL',40),
                               ('X',10), ('IX',9), ('V',5), ('IV',4), ('I',1)])
    out = ''
    for key, value in conversions.items():
      while num >= value:
        out += key
        num -= value
    return out
  
  @classmethod
  def from_roman(self, roman):
    conversions = OrderedDict([('CM',900), ('CD',400), ('XC',90), ('XL',40), ('IX',9), ('IV',4), ('M',1000), ('D',500),
                               ('C',100), ('L',50), ('X',10), ('V',5), ('I',1)])
    out = 0
    for key, value in conversions.items():
      out += value * roman.count(key)
      roman = string.replace(roman, key, "")
    return out



from collections import OrderedDict
import re


ROMAN_NUMERALS = OrderedDict([
    ('M', 1000),
    ('CM', 900),
    ('D', 500),
    ('CD', 400),
    ('C', 100),
    ('XC', 90),
    ('L', 50),
    ('XL', 40),
    ('X', 10),
    ('IX', 9),
    ('V', 5),
    ('IV', 4),
    ('I', 1),
])

DECIMAL_TO_ROMAN = [(v, k) for k, v in list(ROMAN_NUMERALS.items())]

ROMAN_RE = '|'.join(ROMAN_NUMERALS)


class RomanNumerals(object):
    @staticmethod
    def from_roman(roman):
        return sum(ROMAN_NUMERALS[d] for d in re.findall(ROMAN_RE, roman))

    @staticmethod
    def to_roman(decimal):
        result = []
        for number, roman in DECIMAL_TO_ROMAN:
            while decimal >= number:
                decimal -= number
                result.append(roman)
        return ''.join(result)


from itertools import groupby

"""A RomanNumerals helper object"""
class RomanNumerals(object):
    letters = [('M',1000), ('CM',900), ('D',500), ('CD',400), ('C',100), ('XC',90),
               ('L',50), ('XL',40), ('X',10), ('IX',9), ('V',5), ('IV',4), ('I',1)]
    
    @classmethod
    def to_roman(cls, val):
        rom = []
        for l, v in cls.letters:
            m = val // v
            rom += m*[l]
            val -= m*v
        return ''.join(rom)
        
    @classmethod
    def from_roman(cls, rom):
        cumu = 0
        for l, v in cls.letters:
            while rom[:len(l)] == l:
                rom = rom[len(l):]
                cumu += v
            if not rom: break
        return cumu
                



class RomanNumerals():
    READ = { 1:'I',  5:'V', 10:'X', 50:'L', 100:'C', 500:'D', 1000:'M' }
    INTG = { v:k for k,v in list(READ.items())}
    
    
    @classmethod
    def to_roman(cls, num, rom = ''):
        EDGE = {
              0:lambda x, elem,  dict, number : dict.get(elem, '') * number, 
              1:lambda e, i, D, l : e + D[i*(l+1)] if l in [4,9] else D[i*l] if l is 5 else D[i*5] + (e * (l%5))
               }
        for element in list(cls.READ.keys())[0::2][::-1]: 
            left = num // element
            rom += EDGE.get(left>3, '')( cls.READ[element] , element, cls.READ, left )
            num %= element
        return rom
        
    @classmethod   
    def from_roman(cls, roma): 
        cls.INTG.update({'N':0}) 
        roma += 'N'
        return sum( [-cls.INTG[x] if cls.INTG[x] < cls.INTG[z] else cls.INTG[x] for x,z in zip(roma[:-1], roma[1:])]) 



class RomanNumerals:
    @staticmethod
    def from_roman(s):
        X=[dict(zip('MDCLXVI',(1e3,500,100,50,10,5,1)))[x]for x in s]
        return int(sum((x,-x)[x<y]for x,y in zip(X,X[1:]))+X[-1])
    @staticmethod
    def to_roman(i,o=' I II III IV V VI VII VIII IX'.split(' ')):
        r=lambda n:o[n]if n<10 else''.join(dict(zip('IVXLC','XLCDM'))[c]for c in r(n//10))+o[n%10]
        return r(i)

"""TODO: create a RomanNumerals helper object"""
class RomanNumerals:

    @staticmethod
    def to_roman(num):
        ints = (1000, 900,  500, 400, 100,  90, 50,  40, 10,  9,   5,  4,   1)
        nums = ('M',  'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I')
        result = ""
        for i in range(len(ints)):
            count = int(num / ints[i])
            result += str(nums[i] * count)
            num -= ints[i] * count
        return result
        
    @staticmethod
    def from_roman(roman):
        nums = ['M', 'D', 'C', 'L', 'X', 'V', 'I']
        ints = [1000, 500, 100, 50,  10,  5,   1]
        places = []
        for i in range(len(roman)):
            c = roman[i]
            value = ints[nums.index(c)]
            try:
                next_val = ints[nums.index(roman[i+1])]
                if next_val > value:
                    value *= -1
            except:
                pass
                
            places.append(value)
        return sum(places)

class RomanNumerals(object):
    
    @classmethod
    def to_roman(self, n, result='', i=0):
        SPQR = ((1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), (90, 'XC'), (50, 'L'),
        (40, 'XL'), (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I'))
        
        if i == len(SPQR):
            return result
        while n - SPQR[i][0] >= 0:
            result += SPQR[i][1]
            n -= SPQR[i][0]
        i += 1
        return self.to_roman(n, result, i)
        
        
    @classmethod
    def from_roman(self, roman, result=0, i=0):
        SPQR = ((900, 'CM'), (1000, 'M'), (400, 'CD'), (500, 'D'), (90, 'XC'), (100, 'C'), (50, 'L'),
        (40, 'XL'), (9, 'IX'), (10, 'X'), (4, 'IV'), (5, 'V'), (1, 'I'))
        
        if roman == '':
            return result
        while SPQR[i][1] in roman:
            result += SPQR[i][0]
            roman = roman[len(SPQR[i][1]):]
        i += 1
        return self.from_roman(roman, result, i)

from functools import reduce
class RomanNumerals:

    _map = {1000: 'M', 900: 'CM', 500: 'D', 400: 'CD', 100: 'C', 90: 'XC',
            50: 'L', 40: 'XL', 10: 'X', 9: 'IX', 5: 'V', 4: 'IV', 1: 'I'}
    
    @classmethod
    def to_roman(cls, n):
        _map = cls._map
        red = lambda s, e: (s[0]+_map[e]*(s[1]//e), s[1]-e*(s[1]//e))
        return reduce(red, reversed(sorted(_map)), ['', n])[0]
    
    @classmethod
    def from_roman(cls, s):
        _map = {v:k for k, v in cls._map.items()}
        red = lambda s, e: s[:-1] + [s[-1] + e] if s and s[-1] + e in _map else s + [e]
        return sum(_map[x] for x in reduce(red, s, []))


from itertools import groupby, zip_longest

FROM_ROMAN = {'M': 1000, 'D': 500, 'C': 100, 'L': 50, 'X': 10, 'V': 5, 'I': 1}
TO_ROMAN = ((1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'),
            (100, 'C'), (90, 'XC'), (50, 'L'), (40, 'XL'), (10, 'X'),
            (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I'))


class RomanNumerals(object):
    @staticmethod
    def to_roman(n):
        result = []
        for num, char in TO_ROMAN:
            q, n = divmod(n, num)
            result.append(q * char)
        return ''.join(result)

    @staticmethod
    def from_roman(roman):
        pairs = [sum(g) for _, g in groupby(FROM_ROMAN[a] for a in roman)]
        return sum(a + b if a > b else b - a for a, b in
                   zip_longest(pairs[::2], pairs[1::2], fillvalue=0))


class RomanNumerals:
    dic = [(1000, 'M'), (900, 'CM'), (500, 'D'), (400, 'CD'), (100, 'C'), (90, 'XC'), (50, 'L'), (40, 'XL'), (10, 'X'), (9, 'IX'), (5, 'V'), (4, 'IV'), (1, 'I')]

    @staticmethod
    def to_roman(n):        
        res = []        
        for i in RomanNumerals.dic:
            res.extend((n / i[0]) * i[1])
            n -= (n / i[0]) * i[0]
        
        return "".join(res)
    
    @staticmethod
    def from_roman(n):
        res = 0
        for i in RomanNumerals.dic:
            while n.startswith(i[1]):
                res += i[0]
                n = n[len(i[1]):]
        return res