RosettaCodeData/Task/Magic-squares-of-odd-order/JavaScript/magic-squares-of-odd-order-3.js
2017-09-25 22:28:19 +02:00

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))))));
})();