RosettaCodeData/Task/CUSIP/Python/cusip-2.py

161 lines
3.7 KiB
Python

'''CUSIP'''
from itertools import (cycle, islice, starmap)
from functools import (reduce)
from operator import (add)
from enum import (Enum)
# isCusip :: Dict -> String -> Bool
def isCusip(dct):
'''Test for the validity of a CUSIP string in the
context of a supplied dictionary of char values'''
def go(s):
ns = [dct[c] for c in list(s) if c in dct]
return 9 == len(ns) and (
ns[-1] == (
10 - (
sum(zipWith(
lambda f, x: add(*divmod(f(x), 10))
)(cycle([identity, double]))(
take(8)(ns)
)) % 10
)
) % 10
)
return go
# cusipCharDict :: () -> Dict Char Int
def cusipCharDict():
'''Dictionary of integer values for CUSIP characters'''
def kv(a, ic):
i, c = ic
a[c] = i
return a
return reduce(
kv,
enumerate(
enumFromTo('0')('9') + (
enumFromTo('A')('Z') + list('*&#')
)
),
{}
)
# TEST -------------------------------------------------
# main :: IO ()
def main():
'''Tests'''
# cusipTest :: String -> Bool
cusipTest = isCusip(cusipCharDict())
print(
tabulated('Valid as CUSIP string:')(
cusipTest
)([
'037833100',
'17275R102',
'38259P508',
'594918104',
'68389X106',
'68389X105'
])
)
# GENERIC -------------------------------------------------
# double :: Num -> Num
def double(x):
'''Wrapped here as a function for the zipWith expression'''
return 2 * x
# enumFromTo :: Enum a => a -> a -> [a]
def enumFromTo(m):
'''Enumeration of values [m..n]'''
def go(x, y):
t = type(m)
i = fromEnum(x)
d = 0 if t != float else (x - i)
return list(map(
lambda x: toEnum(t)(d + x),
range(i, 1 + fromEnum(y))
) if int != t else range(x, 1 + y))
return lambda n: go(m, n)
# fromEnum :: Enum a => a -> Int
def fromEnum(x):
'''Index integer for enumerable value.'''
return ord(x) if str == type(x) else (
x.value if isinstance(x, Enum) else int(x)
)
# mul :: Num -> Num -> Num
def mul(x):
'''Function version of (*) operator;
a curried equivalent of operator.mul'''
return lambda y: x * y
# identity :: a -> a
def identity(x):
'''The identity function.
The usual 'id' is reserved in Python.'''
return x
# tabulated :: String -> (a -> b) -> [a] -> String
def tabulated(s):
'''heading -> function -> input List -> tabulated output string'''
def go(f, xs):
def width(x):
return len(str(x))
w = width(max(xs, key=width))
return s + '\n' + '\n'.join([
str(x).rjust(w, ' ') + ' -> ' + str(f(x)) for x in xs
])
return lambda f: lambda xs: go(f, xs)
# take :: Int -> [a] -> [a]
# take :: Int -> String -> String
def take(n):
'''The prefix of xs of length n,
or xs itself if n > length xs.'''
return lambda xs: (
xs[0:n]
if isinstance(xs, list)
else list(islice(xs, n))
)
# toEnum :: Type -> Int -> a
def toEnum(t):
'''Enumerable value from index integer'''
dct = {
int: int,
float: float,
str: chr,
bool: bool
}
return lambda x: dct[t](x) if t in dct else t(x)
# zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
def zipWith(f):
'''Zipping with a custom (rather than tuple) function'''
return lambda xs: lambda ys: (
list(starmap(f, zip(xs, ys)))
)
# MAIN ---
if __name__ == '__main__':
main()