161 lines
3.7 KiB
Python
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()
|