167 lines
5.3 KiB
JavaScript
167 lines
5.3 KiB
JavaScript
(() => {
|
|
|
|
// Number of rows -> n rows of integers
|
|
// oddMagicTable :: Int -> [[Int]]
|
|
const oddMagicTable = n =>
|
|
mapAsTable(n, siamMap(quot(n, 2)));
|
|
|
|
// Highest index of square -> Siam xys so far -> xy -> next xy coordinate
|
|
// nextSiam :: Int -> M.Map (Int, Int) Int -> (Int, Int) -> (Int, Int)
|
|
const nextSiam = (uBound, sMap, [x, y]) => {
|
|
const [a, b] = [x + 1, y - 1];
|
|
return (a > uBound && b < 0) ? (
|
|
[uBound, 1] // Move down if obstructed by corner
|
|
) : a > uBound ? (
|
|
[0, b] // Wrap at right edge
|
|
) : b < 0 ? (
|
|
[a, uBound] // Wrap at upper edge
|
|
) : mapLookup(sMap, [a, b])
|
|
.nothing ? ( // Unimpeded default: one up one right
|
|
[a, b]
|
|
) : [a - 1, b + 2]; // Position occupied: move down
|
|
};
|
|
|
|
// Order of table -> Siamese indices keyed by coordinates
|
|
// siamMap :: Int -> M.Map (Int, Int) Int
|
|
const siamMap = n => {
|
|
const
|
|
uBound = 2 * n,
|
|
sPath = (uBound, sMap, xy, n) => {
|
|
const [x, y] = xy,
|
|
newMap = mapInsert(sMap, xy, n);
|
|
return (y == uBound && x == quot(uBound, 2) ? (
|
|
newMap
|
|
) : sPath(
|
|
uBound, newMap, nextSiam(uBound, newMap, [x, y]), n + 1));
|
|
};
|
|
return sPath(uBound, {}, [n, 0], 1);
|
|
};
|
|
|
|
// Size of square -> integers keyed by coordinates -> rows of integers
|
|
// mapAsTable :: Int -> M.Map (Int, Int) Int -> [[Int]]
|
|
const mapAsTable = (nCols, dct) => {
|
|
const axis = enumFromTo(0, nCols - 1);
|
|
return map(row => map(k => fromJust(mapLookup(dct, k)), row),
|
|
bind(axis, y => [bind(axis, x => [
|
|
[x, y]
|
|
])]));
|
|
};
|
|
|
|
// GENERIC FUNCTIONS ------------------------------------------------------
|
|
|
|
// bind :: [a] -> (a -> [b]) -> [b]
|
|
const bind = (xs, f) => [].concat.apply([], xs.map(f));
|
|
|
|
// curry :: Function -> Function
|
|
const curry = (f, ...args) => {
|
|
const go = xs => xs.length >= f.length ? (f.apply(null, xs)) :
|
|
function () {
|
|
return go(xs.concat(Array.from(arguments)));
|
|
};
|
|
return go([].slice.call(args, 1));
|
|
};
|
|
|
|
// enumFromTo :: Int -> Int -> [Int]
|
|
const enumFromTo = (m, n) =>
|
|
Array.from({
|
|
length: Math.floor(n - m) + 1
|
|
}, (_, i) => m + i);
|
|
|
|
// fromJust :: M a -> a
|
|
const fromJust = m => m.nothing ? {} : m.just;
|
|
|
|
// fst :: [a, b] -> a
|
|
const fst = pair => pair.length === 2 ? pair[0] : undefined;
|
|
|
|
// intercalate :: String -> [a] -> String
|
|
const intercalate = (s, xs) => xs.join(s);
|
|
|
|
// justifyRight :: Int -> Char -> Text -> Text
|
|
const justifyRight = (n, cFiller, strText) =>
|
|
n > strText.length ? (
|
|
(cFiller.repeat(n) + strText)
|
|
.slice(-n)
|
|
) : strText;
|
|
|
|
// length :: [a] -> Int
|
|
const length = xs => xs.length;
|
|
|
|
// log :: a -> IO ()
|
|
const log = (...args) =>
|
|
console.log(
|
|
args
|
|
.map(show)
|
|
.join(' -> ')
|
|
);
|
|
|
|
// map :: (a -> b) -> [a] -> [b]
|
|
const map = (f, xs) => xs.map(f);
|
|
|
|
// mapInsert :: Dictionary -> k -> v -> Dictionary
|
|
const mapInsert = (dct, k, v) =>
|
|
(dct[(typeof k === 'string' && k) || show(k)] = v, dct);
|
|
|
|
// mapKeys :: Map k a -> [k]
|
|
const mapKeys = dct =>
|
|
sortBy(mappendComparing([snd, fst]),
|
|
map(JSON.parse, Object.keys(dct)));
|
|
|
|
// mapLookup :: Dictionary -> k -> Maybe v
|
|
const mapLookup = (dct, k) => {
|
|
const
|
|
v = dct[(typeof k === 'string' && k) || show(k)],
|
|
blnJust = (typeof v !== 'undefined');
|
|
return {
|
|
nothing: !blnJust,
|
|
just: v
|
|
};
|
|
};
|
|
|
|
// mappendComparing :: [(a -> b)] -> (a -> a -> Ordering)
|
|
const mappendComparing = fs => (x, y) =>
|
|
fs.reduce((ord, f) => {
|
|
if (ord !== 0) return ord;
|
|
const
|
|
a = f(x),
|
|
b = f(y);
|
|
return a < b ? -1 : a > b ? 1 : 0
|
|
}, 0);
|
|
|
|
// maximum :: [a] -> a
|
|
const maximum = xs =>
|
|
xs.reduce((a, x) => (x > a || a === undefined ? x : a), undefined);
|
|
|
|
// Integral a => a -> a -> a
|
|
const quot = (n, m) => Math.floor(n / m);
|
|
|
|
// show :: a -> String
|
|
const show = x => JSON.stringify(x);
|
|
//
|
|
// snd :: (a, b) -> b
|
|
const snd = tpl => Array.isArray(tpl) ? tpl[1] : undefined;
|
|
//
|
|
// sortBy :: (a -> a -> Ordering) -> [a] -> [a]
|
|
const sortBy = (f, xs) => xs.slice()
|
|
.sort(f);
|
|
|
|
// table :: String -> [[String]] -> [String]
|
|
const table = (delim, rows) =>
|
|
map(curry(intercalate)(delim),
|
|
transpose(map(col =>
|
|
map(curry(justifyRight)(maximum(map(length, col)))(' '), col),
|
|
transpose(rows))));
|
|
|
|
// transpose :: [[a]] -> [[a]]
|
|
const transpose = xs =>
|
|
xs[0].map((_, col) => xs.map(row => row[col]));
|
|
|
|
// unlines :: [String] -> String
|
|
const unlines = xs => xs.join('\n');
|
|
|
|
// TEST -------------------------------------------------------------------
|
|
|
|
return intercalate('\n\n',
|
|
bind([3, 5, 7],
|
|
n => unlines(table(" ",
|
|
map(xs => map(show, xs), oddMagicTable(n))))));
|
|
})();
|