185 lines
5.5 KiB
JavaScript
185 lines
5.5 KiB
JavaScript
(() => {
|
|
'use strict';
|
|
|
|
// GENERIC FUNCTIONS ----------------------------------------------------
|
|
|
|
// permutationsWithRepetition :: Int -> [a] -> [[a]]
|
|
const permutationsWithRepetition = (n, as) =>
|
|
as.length > 0 ? (
|
|
foldl1(curry(cartesianProduct)(as), replicate(n, as))
|
|
) : [];
|
|
|
|
// cartesianProduct :: [a] -> [b] -> [[a, b]]
|
|
const cartesianProduct = (xs, ys) =>
|
|
[].concat.apply([], xs.map(x =>
|
|
[].concat.apply([], ys.map(y => [[x].concat(y)]))));
|
|
|
|
// curry :: ((a, b) -> c) -> a -> b -> c
|
|
const curry = f => a => b => f(a, b);
|
|
|
|
// flip :: (a -> b -> c) -> b -> a -> c
|
|
const flip = f => (a, b) => f.apply(null, [b, a]);
|
|
|
|
// foldl1 :: (a -> a -> a) -> [a] -> a
|
|
const foldl1 = (f, xs) =>
|
|
xs.length > 0 ? xs.slice(1)
|
|
.reduce(f, xs[0]) : [];
|
|
|
|
// replicate :: Int -> a -> [a]
|
|
const replicate = (n, a) => {
|
|
let v = [a],
|
|
o = [];
|
|
if (n < 1) return o;
|
|
while (n > 1) {
|
|
if (n & 1) o = o.concat(v);
|
|
n >>= 1;
|
|
v = v.concat(v);
|
|
}
|
|
return o.concat(v);
|
|
};
|
|
|
|
// group :: Eq a => [a] -> [[a]]
|
|
const group = xs => groupBy((a, b) => a === b, 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,
|
|
blnGroup = h !== undefined && f(h, x);
|
|
|
|
return {
|
|
active: blnGroup ? a.active.concat(x) : [x],
|
|
sofar: blnGroup ? a.sofar : a.sofar.concat([a.active])
|
|
};
|
|
}, {
|
|
active: xs.length > 0 ? [xs[0]] : [],
|
|
sofar: []
|
|
});
|
|
return dct.sofar.concat(dct.active.length > 0 ? [dct.active] : []);
|
|
};
|
|
|
|
// compare :: a -> a -> Ordering
|
|
const compare = (a, b) => a < b ? -1 : (a > b ? 1 : 0);
|
|
|
|
// on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
|
|
const on = (f, g) => (a, b) => f(g(a), g(b));
|
|
|
|
// nub :: [a] -> [a]
|
|
const nub = xs => nubBy((a, b) => a === b, xs);
|
|
|
|
// nubBy :: (a -> a -> Bool) -> [a] -> [a]
|
|
const nubBy = (p, xs) => {
|
|
const x = xs.length ? xs[0] : undefined;
|
|
|
|
return x !== undefined ? [x].concat(
|
|
nubBy(p, xs.slice(1)
|
|
.filter(y => !p(x, y)))
|
|
) : [];
|
|
};
|
|
|
|
// find :: (a -> Bool) -> [a] -> Maybe a
|
|
const find = (f, xs) => {
|
|
for (var i = 0, lng = xs.length; i < lng; i++) {
|
|
if (f(xs[i], i)) return xs[i];
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
// Int -> [a] -> [a]
|
|
const take = (n, xs) => xs.slice(0, n);
|
|
|
|
// unlines :: [String] -> String
|
|
const unlines = xs => xs.join('\n');
|
|
|
|
// show :: a -> String
|
|
const show = x => JSON.stringify(x); //, null, 2);
|
|
|
|
// head :: [a] -> a
|
|
const head = xs => xs.length ? xs[0] : undefined;
|
|
|
|
// tail :: [a] -> [a]
|
|
const tail = xs => xs.length ? xs.slice(1) : undefined;
|
|
|
|
// length :: [a] -> Int
|
|
const length = xs => xs.length;
|
|
|
|
|
|
// SIGNED DIGIT SEQUENCES (mapped to sums and to strings)
|
|
|
|
// data Sign :: [ 0 | 1 | -1 ] = ( Unsigned | Plus | Minus )
|
|
// asSum :: [Sign] -> Int
|
|
const asSum = xs => {
|
|
const dct = xs.reduceRight((a, sign, i) => {
|
|
const d = i + 1; // zero-based index to [1-9] positions
|
|
if (sign !== 0) { // Sum increased, digits cleared
|
|
return {
|
|
digits: [],
|
|
n: a.n + (sign * parseInt([d].concat(a.digits)
|
|
.join(''), 10))
|
|
};
|
|
} else return { // Digits extended, sum unchanged
|
|
digits: [d].concat(a.digits),
|
|
n: a.n
|
|
};
|
|
}, {
|
|
digits: [],
|
|
n: 0
|
|
});
|
|
return dct.n + (dct.digits.length > 0 ? (
|
|
parseInt(dct.digits.join(''), 10)
|
|
) : 0);
|
|
};
|
|
|
|
// data Sign :: [ 0 | 1 | -1 ] = ( Unsigned | Plus | Minus )
|
|
// asString :: [Sign] -> String
|
|
const asString = xs => {
|
|
const ns = xs.reduce((a, sign, i) => {
|
|
const d = (i + 1)
|
|
.toString();
|
|
return (sign === 0 ? (
|
|
a + d
|
|
) : (a + (sign > 0 ? ' +' : ' -') + d));
|
|
}, '');
|
|
|
|
return ns[0] === '+' ? tail(ns) : ns;
|
|
};
|
|
|
|
|
|
// SUM T0 100 ------------------------------------------------------------
|
|
|
|
// universe :: [[Sign]]
|
|
const universe = permutationsWithRepetition(9, [0, 1, -1])
|
|
.filter(x => x[0] !== 1);
|
|
|
|
// allNonNegativeSums :: [Int]
|
|
const allNonNegativeSums = universe.map(asSum)
|
|
.filter(x => x >= 0)
|
|
.sort();
|
|
|
|
// uniqueNonNegativeSums :: [Int]
|
|
const uniqueNonNegativeSums = nub(allNonNegativeSums);
|
|
|
|
|
|
return [
|
|
"Sums to 100:\n",
|
|
unlines(universe.filter(x => asSum(x) === 100)
|
|
.map(asString)),
|
|
|
|
"\n\n10 commonest sums (sum, followed by number of routes to it):\n",
|
|
show(take(10, group(allNonNegativeSums)
|
|
.sort(on(flip(compare), length))
|
|
.map(xs => [xs[0], xs.length]))),
|
|
|
|
"\n\nFirst positive integer not expressible as a sum of this kind:\n",
|
|
show(find(
|
|
(x, i) => x !== i,
|
|
uniqueNonNegativeSums.sort(compare)
|
|
) - 1), // i is the the zero-based Array index.
|
|
|
|
"\n10 largest sums:\n",
|
|
show(take(10, uniqueNonNegativeSums.sort(flip(compare))))
|
|
].join('\n') + '\n';
|
|
})();
|