RosettaCodeData/Task/Roman-numerals-Decode/Python/roman-numerals-decode-4.py

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()