212 lines
6.7 KiB
JavaScript
212 lines
6.7 KiB
JavaScript
(() => {
|
|
// main :: IO String
|
|
const main = () => {
|
|
|
|
// Strings derived from integers,
|
|
// and split into [(initial string, final character)] tuples.
|
|
|
|
// xs :: [(String, Char)]
|
|
const xs = map(n => fanArrow(init, last)(n.toString()), [
|
|
12, 127, 28, 42, 39, 113, 42, 18, 44, 118, 44, 37, 113, 124,
|
|
37, 48, 127, 36, 29, 31, 125, 139, 131, 115, 105, 132, 104,
|
|
123, 35, 113, 122, 42, 117, 119, 58, 109, 23, 105, 63, 27,
|
|
44, 105, 99, 41, 128, 121, 116, 125, 32, 61, 37, 127, 29, 113,
|
|
121, 58, 114, 126, 53, 114, 96, 25, 109, 7, 31, 141, 46, 13, 27,
|
|
43, 117, 116, 27, 7, 68, 40, 31, 115, 124, 42, 128, 52, 71, 118,
|
|
117, 38, 27, 106, 33, 117, 116, 111, 40, 119, 47, 105, 57, 122,
|
|
109, 124, 115, 43, 120, 43, 27, 27, 18, 28, 48, 125, 107, 114,
|
|
34, 133, 45, 120, 30, 127, 31, 116, 146
|
|
]);
|
|
|
|
// Re-reading the initial strings as Ints
|
|
// (empty strings read as 0),
|
|
|
|
// ns :: [(Int, Char)]
|
|
const ns = map(x => {
|
|
const s = fst(x);
|
|
return Tuple(s.length > 0 ? (
|
|
parseInt(s, 10)
|
|
) : 0, snd(x));
|
|
}, xs);
|
|
|
|
// and sorting and grouping by these initial Ints,
|
|
// interpreting them as data-collection bins.
|
|
|
|
// bins :: [[(Int, Char)]]
|
|
const bins =
|
|
groupBy(
|
|
(a, b) => a[0] === b[0],
|
|
sortBy(mappendComparing([
|
|
[fst, true],
|
|
[snd, true]
|
|
]), ns)
|
|
);
|
|
|
|
// Forming bars by the ordered accumulation of
|
|
// final characters in each bin,
|
|
|
|
// bars :: [(Int, String)]
|
|
const bars = map(
|
|
fanArrow(
|
|
x => fst(x[0]),
|
|
x => map(snd, x)
|
|
),
|
|
bins
|
|
);
|
|
|
|
// and obtaining a complete series, with empty bars
|
|
// interpolated for any missing integers.
|
|
|
|
// series :: [(Int, String)]
|
|
const series = concat(mapAccumL(
|
|
(a, x) => {
|
|
const n = x[0];
|
|
return a !== n ? (
|
|
Tuple(1 + n,
|
|
map(i => Tuple(i, []),
|
|
enumFromToInt(a, n - 1)
|
|
)
|
|
.concat([x])
|
|
)
|
|
) : Tuple(1 + a, [x]);
|
|
}, 7, bars
|
|
)[1]);
|
|
|
|
// Assembling the series as a list of strings with
|
|
// right-justified indices,
|
|
|
|
// plotLines :: [String]
|
|
const plotLines = foldr(
|
|
(x, a) => cons(concat([
|
|
justifyRight(2, ' ', x[0].toString()),
|
|
' | ',
|
|
unwords(x[1])
|
|
]), a), [],
|
|
series
|
|
);
|
|
|
|
// and passing these over to IO as a single
|
|
// newline-delimited string.
|
|
|
|
return unlines(plotLines);
|
|
};
|
|
|
|
// GENERIC FUNCTIONS -----------------------------------------------------
|
|
|
|
// Tuple (,) :: a -> b -> (a, b)
|
|
const Tuple = (a, b) => ({
|
|
type: 'Tuple',
|
|
'0': a,
|
|
'1': b
|
|
});
|
|
|
|
// compare :: a -> a -> Ordering
|
|
const compare = (a, b) => a < b ? -1 : (a > b ? 1 : 0);
|
|
|
|
// concat :: [[a]] -> [a]
|
|
// concat :: [String] -> String
|
|
const concat = xs =>
|
|
xs.length > 0 ? (() => {
|
|
const unit = typeof xs[0] === 'string' ? '' : [];
|
|
return unit.concat.apply(unit, xs);
|
|
})() : [];
|
|
|
|
// cons :: a -> [a] -> [a]
|
|
const cons = (x, xs) => [x, ...xs];
|
|
|
|
// enumFromToInt :: Int -> Int -> [Int]
|
|
const enumFromToInt = (m, n) =>
|
|
n >= m ? Array.from({
|
|
length: Math.floor(n - m) + 1
|
|
}, (_, i) => m + i) : [];
|
|
|
|
// Compose a function from a simple value to a tuple of
|
|
// the separate outputs of two different functions
|
|
// fanArrow (&&&) :: (a -> b) -> (a -> c) -> (a -> (b, c))
|
|
const fanArrow = (f, g) => x => Tuple(f(x), g(x));
|
|
|
|
// flip :: (a -> b -> c) -> b -> a -> c
|
|
const flip = f => (a, b) => f.apply(null, [b, a]);
|
|
|
|
// Note that that the Haskell signature of foldr is different from that of
|
|
// foldl - the positions of accumulator and current value are reversed
|
|
// foldr :: (a -> b -> b) -> b -> [a] -> b
|
|
const foldr = (f, a, xs) => xs.reduceRight(flip(f), a);
|
|
|
|
// fst :: (a, b) -> a
|
|
const fst = tpl => tpl.type !== 'Tuple' ? undefined : tpl[0];
|
|
|
|
// Typical usage: groupBy(on(eq, f), xs)
|
|
// groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
|
|
const groupBy = (f, xs) => {
|
|
const dct = xs.slice(1)
|
|
.reduce((a, x) => {
|
|
const h = a.active.length > 0 ? a.active[0] : undefined;
|
|
return h !== undefined && f(h, x) ? {
|
|
active: a.active.concat([x]),
|
|
sofar: a.sofar
|
|
} : {
|
|
active: [x],
|
|
sofar: a.sofar.concat([a.active])
|
|
};
|
|
}, {
|
|
active: xs.length > 0 ? [xs[0]] : [],
|
|
sofar: []
|
|
});
|
|
return dct.sofar.concat(dct.active.length > 0 ? [dct.active] : []);
|
|
};
|
|
|
|
// init :: [a] -> [a]
|
|
const init = xs => xs.length > 0 ? xs.slice(0, -1) : undefined;
|
|
|
|
// justifyRight :: Int -> Char -> String -> String
|
|
const justifyRight = (n, cFiller, strText) =>
|
|
n > strText.length ? (
|
|
(cFiller.repeat(n) + strText)
|
|
.slice(-n)
|
|
) : strText;
|
|
|
|
// last :: [a] -> a
|
|
const last = xs => xs.length ? xs.slice(-1)[0] : undefined;
|
|
|
|
// map :: (a -> b) -> [a] -> [b]
|
|
const map = (f, xs) => xs.map(f);
|
|
|
|
// mapAccumL :: (acc -> x -> (acc, y)) -> acc -> [x] -> (acc, [y])
|
|
const mapAccumL = (f, acc, xs) =>
|
|
xs.reduce((a, x, i) => {
|
|
const pair = f(a[0], x, i);
|
|
return Tuple(pair[0], a[1].concat(pair[1]));
|
|
}, Tuple(acc, []));
|
|
|
|
// mappendComparing :: [((a -> b), Bool)] -> (a -> a -> Ordering)
|
|
const mappendComparing = fboolPairs =>
|
|
(x, y) => fboolPairs.reduce(
|
|
(ordr, fb) => {
|
|
const f = fb[0];
|
|
return ordr !== 0 ? (
|
|
ordr
|
|
) : fb[1] ? (
|
|
compare(f(x), f(y))
|
|
) : compare(f(y), f(x));
|
|
}, 0
|
|
);
|
|
|
|
// snd :: (a, b) -> b
|
|
const snd = tpl => tpl.type !== 'Tuple' ? undefined : tpl[1];
|
|
|
|
// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
|
|
const sortBy = (f, xs) =>
|
|
xs.slice()
|
|
.sort(f);
|
|
|
|
// unlines :: [String] -> String
|
|
const unlines = xs => xs.join('\n');
|
|
|
|
// unwords :: [String] -> String
|
|
const unwords = xs => xs.join(' ');
|
|
|
|
// MAIN ------------------------------------------------------------------
|
|
return main();
|
|
})();
|