205 lines
5.5 KiB
Python
205 lines
5.5 KiB
Python
'''Chinese remainder theorem'''
|
|
|
|
from operator import (add, mul)
|
|
from functools import reduce
|
|
|
|
|
|
# cnRemainder :: [Int] -> [Int] -> Either String Int
|
|
def cnRemainder(ms):
|
|
'''Chinese remainder theorem.
|
|
(moduli, residues) -> Either explanation or solution
|
|
'''
|
|
def go(ms, rs):
|
|
mp = numericProduct(ms)
|
|
cms = [(mp // x) for x in ms]
|
|
|
|
def possibleSoln(invs):
|
|
return Right(
|
|
sum(map(
|
|
mul,
|
|
cms, map(mul, rs, invs)
|
|
)) % mp
|
|
)
|
|
return bindLR(
|
|
zipWithEither(modMultInv)(cms)(ms)
|
|
)(possibleSoln)
|
|
|
|
return lambda rs: go(ms, rs)
|
|
|
|
|
|
# modMultInv :: Int -> Int -> Either String Int
|
|
def modMultInv(a, b):
|
|
'''Modular multiplicative inverse.'''
|
|
x, y = eGcd(a, b)
|
|
return Right(x) if 1 == (a * x + b * y) else (
|
|
Left('no modular inverse for ' + str(a) + ' and ' + str(b))
|
|
)
|
|
|
|
|
|
# egcd :: Int -> Int -> (Int, Int)
|
|
def eGcd(a, b):
|
|
'''Extended greatest common divisor.'''
|
|
def go(a, b):
|
|
if 0 == b:
|
|
return (1, 0)
|
|
else:
|
|
q, r = divmod(a, b)
|
|
(s, t) = go(b, r)
|
|
return (t, s - q * t)
|
|
return go(a, b)
|
|
|
|
|
|
# TEST ----------------------------------------------------
|
|
# main :: IO ()
|
|
def main():
|
|
'''Tests of soluble and insoluble cases.'''
|
|
|
|
print(
|
|
fTable(
|
|
__doc__ + ':\n\n (moduli, residues) -> ' + (
|
|
'Either solution or explanation\n'
|
|
)
|
|
)(repr)(
|
|
either(compose(quoted("'"))(curry(add)('No solution: ')))(
|
|
compose(quoted(' '))(repr)
|
|
)
|
|
)(uncurry(cnRemainder))([
|
|
([10, 4, 12], [11, 12, 13]),
|
|
([11, 12, 13], [10, 4, 12]),
|
|
([10, 4, 9], [11, 22, 19]),
|
|
([3, 5, 7], [2, 3, 2]),
|
|
([2, 3, 2], [3, 5, 7])
|
|
])
|
|
)
|
|
|
|
|
|
# GENERIC -------------------------------------------------
|
|
|
|
# Left :: a -> Either a b
|
|
def Left(x):
|
|
'''Constructor for an empty Either (option type) value
|
|
with an associated string.'''
|
|
return {'type': 'Either', 'Right': None, 'Left': x}
|
|
|
|
|
|
# Right :: b -> Either a b
|
|
def Right(x):
|
|
'''Constructor for a populated Either (option type) value'''
|
|
return {'type': 'Either', 'Left': None, 'Right': x}
|
|
# any :: (a -> Bool) -> [a] -> Bool
|
|
|
|
|
|
def any_(p):
|
|
'''True if p(x) holds for at least
|
|
one item in xs.'''
|
|
def go(xs):
|
|
for x in xs:
|
|
if p(x):
|
|
return True
|
|
return False
|
|
return lambda xs: go(xs)
|
|
|
|
|
|
# bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
|
|
def bindLR(m):
|
|
'''Either monad injection operator.
|
|
Two computations sequentially composed,
|
|
with any value produced by the first
|
|
passed as an argument to the second.'''
|
|
return lambda mf: (
|
|
mf(m.get('Right')) if None is m.get('Left') else m
|
|
)
|
|
|
|
|
|
# compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
|
|
def compose(g):
|
|
'''Right to left function composition.'''
|
|
return lambda f: lambda x: g(f(x))
|
|
|
|
|
|
# curry :: ((a, b) -> c) -> a -> b -> c
|
|
def curry(f):
|
|
'''A curried function derived
|
|
from an uncurried function.'''
|
|
return lambda a: lambda b: f(a, b)
|
|
|
|
|
|
# either :: (a -> c) -> (b -> c) -> Either a b -> c
|
|
def either(fl):
|
|
'''The application of fl to e if e is a Left value,
|
|
or the application of fr to e if e is a Right value.'''
|
|
return lambda fr: lambda e: fl(e['Left']) if (
|
|
None is e['Right']
|
|
) else fr(e['Right'])
|
|
|
|
|
|
# fTable :: String -> (a -> String) ->
|
|
# (b -> String) -> (a -> b) -> [a] -> String
|
|
def fTable(s):
|
|
'''Heading -> x display function ->
|
|
fx display function ->
|
|
f -> value list -> tabular string.'''
|
|
def go(xShow, fxShow, f, xs):
|
|
w = max(map(compose(len)(xShow), xs))
|
|
return s + '\n' + '\n'.join([
|
|
xShow(x).rjust(w, ' ') + (' -> ') + fxShow(f(x))
|
|
for x in xs
|
|
])
|
|
return lambda xShow: lambda fxShow: lambda f: lambda xs: go(
|
|
xShow, fxShow, f, xs
|
|
)
|
|
|
|
|
|
# numericProduct :: [Num] -> Num
|
|
def numericProduct(xs):
|
|
'''The arithmetic product of all numbers in xs.'''
|
|
return reduce(mul, xs, 1)
|
|
|
|
|
|
# partitionEithers :: [Either a b] -> ([a],[b])
|
|
def partitionEithers(lrs):
|
|
'''A list of Either values partitioned into a tuple
|
|
of two lists, with all Left elements extracted
|
|
into the first list, and Right elements
|
|
extracted into the second list.
|
|
'''
|
|
def go(a, x):
|
|
ls, rs = a
|
|
r = x.get('Right')
|
|
return (ls + [x.get('Left')], rs) if None is r else (
|
|
ls, rs + [r]
|
|
)
|
|
return reduce(go, lrs, ([], []))
|
|
|
|
|
|
# quoted :: Char -> String -> String
|
|
def quoted(c):
|
|
'''A string flanked on both sides
|
|
by a specified quote character.
|
|
'''
|
|
return lambda s: c + s + c
|
|
|
|
|
|
# uncurry :: (a -> b -> c) -> ((a, b) -> c)
|
|
def uncurry(f):
|
|
'''A function over a tuple,
|
|
derived from a curried function.'''
|
|
return lambda xy: f(xy[0])(xy[1])
|
|
|
|
|
|
# zipWithEither :: (a -> b -> Either String c)
|
|
# -> [a] -> [b] -> Either String [c]
|
|
def zipWithEither(f):
|
|
'''Either a list of results if f succeeds with every pair
|
|
in the zip of xs and ys, or an explanatory string
|
|
if any application of f returns no result.
|
|
'''
|
|
def go(xs, ys):
|
|
ls, rs = partitionEithers(map(f, xs, ys))
|
|
return Left(ls[0]) if ls else Right(rs)
|
|
return lambda xs: lambda ys: go(xs, ys)
|
|
|
|
|
|
# MAIN ---
|
|
if __name__ == '__main__':
|
|
main()
|