165 lines
4.4 KiB
JavaScript
165 lines
4.4 KiB
JavaScript
(() => {
|
|
'use strict';
|
|
|
|
const main = () => {
|
|
|
|
// checkSumLR :: String -> Either String String
|
|
const checkSumLR = s => {
|
|
const
|
|
tpl = partitionEithers(map(charValueLR, s));
|
|
return 0 < tpl[0].length ? (
|
|
Left(s + ' -> ' + unwords(tpl[0]))
|
|
) : Right(rem(10 - rem(
|
|
sum(zipWith(
|
|
(a, b) => a * b,
|
|
[1, 3, 1, 7, 3, 9],
|
|
tpl[1]
|
|
)), 10
|
|
), 10).toString());
|
|
};
|
|
|
|
// charValue :: Char -> Either String Int
|
|
const charValueLR = c =>
|
|
isAlpha(c) ? (
|
|
isUpper(c) ? (
|
|
elem(c, 'AEIOU') ? Left(
|
|
'Unexpected vowel: ' + c
|
|
) : Right(ord(c) - ord('A') + 10)
|
|
) : Left('Unexpected lower case character: ' + c)
|
|
) : isDigit(c) ? Right(
|
|
parseInt(c, 10)
|
|
) : Left('Unexpected character: ' + c);
|
|
|
|
// TESTS ------------------------------------------
|
|
const [problems, checks] = Array.from(
|
|
partitionEithers(map(s => bindLR(
|
|
checkSumLR(s),
|
|
c => Right(s + c)
|
|
),
|
|
[
|
|
"710889", "B0YBKJ", "406566",
|
|
"B0YBLH", "228276", "B0YBKL",
|
|
"557910", "B0YBKR", "585284",
|
|
"B0YBKT", "B00030"
|
|
]
|
|
))
|
|
);
|
|
return unlines(
|
|
0 < problems.length ? (
|
|
problems
|
|
) : checks
|
|
);
|
|
};
|
|
|
|
// GENERIC FUNCTIONS ----------------------------
|
|
|
|
// Left :: a -> Either a b
|
|
const Left = x => ({
|
|
type: 'Either',
|
|
Left: x
|
|
});
|
|
|
|
// Right :: b -> Either a b
|
|
const Right = x => ({
|
|
type: 'Either',
|
|
Right: x
|
|
});
|
|
|
|
// Tuple (,) :: a -> b -> (a, b)
|
|
const Tuple = (a, b) => ({
|
|
type: 'Tuple',
|
|
'0': a,
|
|
'1': b,
|
|
length: 2
|
|
});
|
|
|
|
// bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
|
|
const bindLR = (m, mf) =>
|
|
undefined !== m.Left ? (
|
|
m
|
|
) : mf(m.Right);
|
|
|
|
// elem :: Eq a => a -> [a] -> Bool
|
|
const elem = (x, xs) => xs.includes(x);
|
|
|
|
// isAlpha :: Char -> Bool
|
|
const isAlpha = c =>
|
|
/[A-Za-z\u00C0-\u00FF]/.test(c);
|
|
|
|
// isDigit :: Char -> Bool
|
|
const isDigit = c => {
|
|
const n = ord(c);
|
|
return 48 <= n && 57 >= n;
|
|
};
|
|
|
|
// isUpper :: Char -> Bool
|
|
const isUpper = c =>
|
|
/[A-Z]/.test(c);
|
|
|
|
// Returns Infinity over objects without finite length.
|
|
// This enables zip and zipWith to choose the shorter
|
|
// argument when one is non-finite, like cycle, repeat etc
|
|
|
|
// length :: [a] -> Int
|
|
const length = xs =>
|
|
(Array.isArray(xs) || 'string' === typeof xs) ? (
|
|
xs.length
|
|
) : Infinity;
|
|
|
|
// map :: (a -> b) -> [a] -> [b]
|
|
const map = (f, xs) =>
|
|
(Array.isArray(xs) ? (
|
|
xs
|
|
) : xs.split('')).map(f);
|
|
|
|
// ord :: Char -> Int
|
|
const ord = c => c.codePointAt(0);
|
|
|
|
// partitionEithers :: [Either a b] -> ([a],[b])
|
|
const partitionEithers = xs =>
|
|
xs.reduce(
|
|
(a, x) => undefined !== x.Left ? (
|
|
Tuple(a[0].concat(x.Left), a[1])
|
|
) : Tuple(a[0], a[1].concat(x.Right)),
|
|
Tuple([], [])
|
|
);
|
|
|
|
// rem :: Int -> Int -> Int
|
|
const rem = (n, m) => n % m;
|
|
|
|
// sum :: [Num] -> Num
|
|
const sum = xs => xs.reduce((a, x) => a + x, 0);
|
|
|
|
// take :: Int -> [a] -> [a]
|
|
// take :: Int -> String -> String
|
|
const take = (n, xs) =>
|
|
'GeneratorFunction' !== xs.constructor.constructor.name ? (
|
|
xs.slice(0, n)
|
|
) : [].concat.apply([], Array.from({
|
|
length: n
|
|
}, () => {
|
|
const x = xs.next();
|
|
return x.done ? [] : [x.value];
|
|
}));
|
|
|
|
// unlines :: [String] -> String
|
|
const unlines = xs => xs.join('\n');
|
|
|
|
// unwords :: [String] -> String
|
|
const unwords = xs => xs.join(' ');
|
|
|
|
// zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
|
|
const zipWith = (f, xs, ys) => {
|
|
const
|
|
lng = Math.min(length(xs), length(ys)),
|
|
as = take(lng, xs),
|
|
bs = take(lng, ys);
|
|
return Array.from({
|
|
length: lng
|
|
}, (_, i) => f(as[i], bs[i], i));
|
|
};
|
|
|
|
// MAIN ---
|
|
return main();
|
|
})();
|