(() => { // 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]) ); })();