RosettaCodeData/Task/Roman-numerals-Encode/JavaScript/roman-numerals-encode-4.js

66 lines
2.0 KiB
JavaScript

(() => {
// ROMAN INTEGER STRINGS ----------------------------------------------------
// roman :: Int -> String
const roman = n =>
concat(snd(mapAccumL((balance, [k, v]) => {
const [q, r] = quotRem(balance, v);
return [r, q > 0 ? k.repeat(q) : ''];
}, n, [
['M', 1000],
['CM', 900],
['D', 500],
['CD', 400],
['C', 100],
['XC', 90],
['L', 50],
['XL', 40],
['X', 10],
['IX', 9],
['V', 5],
['IV', 4],
['I', 1]
])));
// GENERIC FUNCTIONS -------------------------------------------------------
// concat :: [[a]] -> [a] | [String] -> String
const concat = xs =>
xs.length > 0 ? (() => {
const unit = typeof xs[0] === 'string' ? '' : [];
return unit.concat.apply(unit, xs);
})() : [];
// map :: (a -> b) -> [a] -> [b]
const map = (f, xs) => xs.map(f);
// 'The mapAccumL function behaves like a combination of map and foldl;
// it applies a function to each element of a list, passing an accumulating
// parameter from left to right, and returning a final value of this
// accumulator together with the new list.' (See Hoogle)
// mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
const mapAccumL = (f, acc, xs) =>
xs.reduce((a, x) => {
const pair = f(a[0], x);
return [pair[0], a[1].concat([pair[1]])];
}, [acc, []]);
// quotRem :: Integral a => a -> a -> (a, a)
const quotRem = (m, n) => [Math.floor(m / n), m % n];
// show :: a -> String
const show = (...x) =>
JSON.stringify.apply(
null, x.length > 1 ? [x[0], null, x[1]] : x
);
// snd :: (a, b) -> b
const snd = tpl => Array.isArray(tpl) ? tpl[1] : undefined;
// TEST -------------------------------------------------------------------
return show(
map(roman, [2016, 1990, 2008, 2000, 1666])
);
})();