RosettaCodeData/Task/SEDOLs/JavaScript/sedols-2.js

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