112 lines
2.7 KiB
Python
112 lines
2.7 KiB
Python
'''Roman numerals decoded'''
|
|
|
|
from operator import mul
|
|
from functools import reduce
|
|
from collections import defaultdict
|
|
from itertools import accumulate, chain, cycle
|
|
|
|
|
|
# intFromRoman :: String -> Maybe Int
|
|
def intFromRoman(s):
|
|
'''Just the integer represented by a Roman
|
|
numeral string, or Nothing if any
|
|
characters are unrecognised.
|
|
'''
|
|
dct = defaultdict(
|
|
lambda: None,
|
|
zip(
|
|
'IVXLCDM',
|
|
accumulate(chain([1], cycle([5, 2])), mul)
|
|
)
|
|
)
|
|
|
|
def go(mb, x):
|
|
'''Just a letter value added to or
|
|
subtracted from a total, or Nothing
|
|
if no letter value is defined.
|
|
'''
|
|
if None in (mb, x):
|
|
return None
|
|
else:
|
|
r, total = mb
|
|
return x, total + (-x if x < r else x)
|
|
|
|
return bindMay(reduce(
|
|
go,
|
|
[dct[k.upper()] for k in reversed(list(s))],
|
|
(0, 0)
|
|
))(snd)
|
|
|
|
|
|
# ------------------------- TEST -------------------------
|
|
def main():
|
|
'''Testing a sample of dates.'''
|
|
|
|
print(
|
|
fTable(__doc__ + ':\n')(str)(
|
|
maybe('(Contains unknown character)')(str)
|
|
)(
|
|
intFromRoman
|
|
)([
|
|
"MDCLXVI", "MCMXC", "MMVIII",
|
|
"MMXVI", "MMXVIII", "MMZZIII"
|
|
])
|
|
)
|
|
|
|
|
|
# ----------------------- GENERIC ------------------------
|
|
|
|
# bindMay (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
|
|
def bindMay(m):
|
|
'''Injection operator for the Maybe monad.
|
|
If m is Nothing, it is passed straight through.
|
|
If m is Just(x), the result is an application
|
|
of the (a -> Maybe b) function (mf) to x.'''
|
|
return lambda mf: (
|
|
m if None is m else mf(m)
|
|
)
|
|
|
|
|
|
# maybe :: b -> (a -> b) -> Maybe a -> b
|
|
def maybe(v):
|
|
'''Either the default value v, if m is Nothing,
|
|
or the application of f to x,
|
|
where m is Just(x).
|
|
'''
|
|
return lambda f: lambda m: v if None is m else (
|
|
f(m)
|
|
)
|
|
|
|
|
|
# snd :: (a, b) -> b
|
|
def snd(ab):
|
|
'''Second member of a pair.'''
|
|
return ab[1]
|
|
|
|
|
|
# ---------------------- FORMATTING ----------------------
|
|
|
|
# fTable :: String -> (a -> String) ->
|
|
# (b -> String) -> (a -> b) -> [a] -> String
|
|
def fTable(s):
|
|
'''Heading -> x display function ->
|
|
fx display function -> f -> xs -> tabular string.
|
|
'''
|
|
def go(xShow, fxShow, f, xs):
|
|
ys = [xShow(x) for x in xs]
|
|
w = max(map(len, ys))
|
|
return s + '\n' + '\n'.join(map(
|
|
lambda x, y: (
|
|
f'{y.rjust(w, " ")} -> {fxShow(f(x))}'
|
|
),
|
|
xs, ys
|
|
))
|
|
return lambda xShow: lambda fxShow: lambda f: (
|
|
lambda xs: go(xShow, fxShow, f, xs)
|
|
)
|
|
|
|
|
|
# MAIN ---
|
|
if __name__ == '__main__':
|
|
main()
|