Skip to content

Commit

Permalink
Merge pull request #40 from aollier/roman
Browse files Browse the repository at this point in the history
Roman class
  • Loading branch information
parmentelat authored Nov 21, 2020
2 parents 7ac5daa + d462a5e commit 52c3c9a
Showing 1 changed file with 101 additions and 75 deletions.
176 changes: 101 additions & 75 deletions modules/corrections/cls_roman.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
Args, ExerciseClass, ClassScenario, ClassExpression, ClassStatement)

# @BEG@ name=roman latex_size=footnotesize
import functools
from math import nan, isnan


@functools.total_ordering
class Roman:
"""
a class to implement limited arithmetics on roman numerals
Expand All @@ -19,93 +22,116 @@ class Roman:
"""

def __init__(self, letters_or_integer):
if isinstance(letters_or_integer, str):
self.letters = letters_or_integer.upper()
# compute self.integer
self.letters_to_integer()
elif isinstance(letters_or_integer, int):
self.integer = letters_or_integer
self.integer_to_letters()
if isinstance(letters_or_integer, (int, str)):
try:
integer = int(letters_or_integer)
except ValueError:
letters = letters_or_integer.upper()
self._decimal = Roman.roman_to_decimal(letters)
self._roman = 'N' if isnan(self._decimal) else letters
else:
self._roman = Roman.decimal_to_roman(integer)
self._decimal = nan if self._roman == 'N' else integer
elif isnan(letters_or_integer):
self.letters = 'N'
self.integer = nan
self._decimal = nan
self._roman = 'N'
else:
raise ValueError(
f"cannot initialize Roman from {letters_or_integer}")

raise TypeError(
f"Cannot initialize Roman from {letters_or_integer}")
# @END@

# @BEG@ name=roman latex_size=footnotesize continued=true
def __repr__(self):
return f"{self.letters}={self.integer}"
return f"{self._roman}={self._decimal}"

def __str__(self):
return f"{self._roman}"

def __eq__(self, other):
return self._decimal == other._decimal

def __lt__(self, other):
return self._decimal < other._decimal

def __add__(self, other):
return Roman(self._decimal + other._decimal)

def __sub__(self, other):
return Roman(self._decimal - other._decimal)

def __int__(self):
return self._decimal
# @END@

# @BEG@ name=roman latex_size=footnotesize continued=true
def letters_to_integer(self):
symbols = {
1: 'I',
5: 'V',
10: 'X',
50: 'L',
100: 'C',
500: 'D',
1000: 'M'
}

@staticmethod
def decimal_to_roman(decimal: int) ->str:
"""
return integer conversion
Conversion from decimal number to roman number.
"""
codes = {
'M': 1000,
'D': 500,
'C': 100,
'L': 50,
'X': 10,
'V': 5,
'I': 1,
'N': nan,
}
self.integer = 0
# we scan letters in reverse order
# and keep track of the last one seen
previous = 0

for roman in self.letters[::-1]:
n = codes[roman]
# regular order: add
if n >= previous:
self.integer += n
else:
self.integer -= n
previous = n
if decimal <= 0:
return 'N'

roman = ""
tens = 0

try:
while decimal:
unit = decimal % 10
if unit in (1, 2, 3):
roman = Roman.symbols[10 ** tens] * unit + roman
elif 4 <= unit <= 8:
roman = (Roman.symbols[10 ** tens] * (5 - unit)
+ Roman.symbols[5 * 10 ** tens]
+ Roman.symbols[10 ** tens] * (unit - 5)
+ roman)
elif unit == 9:
roman = (Roman.symbols[10 ** tens]
+ Roman.symbols[10 ** (tens + 1)]
+ roman)
tens += 1
decimal //= 10
except KeyError:
return 'N'
else:
return roman
# @END@

# @BEG@ name=roman latex_size=footnotesize continued=true
def integer_to_letters(self):
codes = {
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 self.integer <= 0 or self.integer >= 4000:
self.integer = nan
self.letters = 'N'
return
n = self.integer
self.letters = ''
while n:
for code, letter in codes.items():
if n >= code:
self.letters += letter
n -= code
break
# inverted symbols
isymbols = {v: k for k, v in symbols.items()}

def __add__(self, other):
return(Roman(self.integer + other.integer))
def __sub__(self, other):
return(Roman(self.integer - other.integer))
def __eq__(self, other):
return self.integer == other.integer
def __int__(self):
return self.integer
@staticmethod
def roman_to_decimal(roman: str) ->int:
"""
Conversion from roman number to decimal number
"""
if not roman:
return nan

decimal = 0
previous = None

try:
for r in roman:
if previous and Roman.isymbols[previous] < Roman.isymbols[r]:
decimal -= 2 * Roman.isymbols[previous]
decimal += Roman.isymbols[r]
previous = r
except KeyError:
return nan
else:
return decimal
# @END@

roman_scenarios = [
Expand Down Expand Up @@ -141,4 +167,4 @@ def __int__(self):
nb_examples=0,
obj_name='R',
header_font_size='small',
)
)

0 comments on commit 52c3c9a

Please sign in to comment.